Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Assembly Language Step by Step Programming with DOS and Linux 2nd Ed 2000.pdf
Скачиваний:
156
Добавлен:
17.08.2013
Размер:
4.44 Mб
Скачать

Simple File I/O

The last example program I present in this book is nominally about working with disk-based text files. However, it pulls together a lot of assembly tricks and features I've explained in previous sections and adds a few more. It's the largest and most complex program in this book, and if you can read it and follow the flow of the logic, you've gotten everything from this book that I set out to teach you. It's more like a "real" program than anything else in this book, in that it works with command-line arguments, writes output to a disk file, and does other useful things that any utility you'll set out to build will likely use.

The program TEXTFILE.ASM creates and fills a text file with text. You can specify the number of lines to be filled in the file, as well as text for the lines. If you don't specify text for the file, the program will generate a line of randomly chosen characters and use that instead. Invoking the program would thus be done like this:

#./textfile 150 Time for tacos!

This invocation would create a new file (the name of which is fixed in the program as "testeroo.txt") and write the text "Time for tacos!" to the file 150 times before closing the file. If the file TESTEROO.TXT already exists, it will be overwritten from the beginning. If you don't type anything after the line count number, the program will fill the file with random alphanumeric characters. If you don't type an integer as the first argument, TEXTFILE will display an error message. If you only type the program name and press Enter, TEXTFILE will display several lines explaining what it is and how to use it.

That's about all there is to say about the program TEXTFILE. The whole program is printed at the end of this chapter. I pull out short sequences for discussion as we go.

Converting Strings into Numbers with sscanf

When you type a number on the command line when invoking a program, you can access that number as one of the command-line arguments, through the mechanisms I described a little earlier in this chapter. However, there's a catch: The number is present as text, and you can't just take the textual string "751" and load it into a register or an integer variable. To make use of numeric arguments as numbers, you must convert their textual expression into numeric form.

The C library has several functions to handle this challenge. Some of them, such as strtod, are pretty specific and limited, and convert text to only one numeric type. One of them, however, has the ability to convert almost any textual expression of a legal numeric value into an appropriate numeric form. This is sscanf, and it's the one we'll look at in TEXTFILE.ASM.

The sscanf function takes three parameters, which you must push on the stack in the following order:

1.First push a pointer to a numeric variable to contain the numeric value generated by sscanf. We're generating a 32-bit integer here, which is also called a double. So, in TEXTFILE.ASM, we pass the address of the memory variable linecount, which is a 32-bit integer.

2.Next push the address of a formatting code string that tells sscanf what numeric format you want the input text to be converted to. Here the code string is "%d," which as you may recall from our printf discussion is the code for doubles (32-bit integers).

3.Finally, push the address of the text string to be converted to the numeric value that it represents. In TEXTFILE.ASM, we push the address of arg(1), which is the first command-line argument you type on the command line when you invoke the program.

Once these three parameters are pushed onto the stack, call sscanf. It returns the converted value in the numeric variable whose address you passed as the first parameter. It also returns a code in EAX to indicate whether the conversion was successful. If the return value in EAX is 0, then an error occurred, and you shouldn't assume you have anything useful in your numeric variable. If the conversion went through successfully, you'll see the number 1 in EAX.

This is the simplest way to use sscanf. It can convert whole arrays of numbers at once, but this is a more specialized use that you're unlikely to need when you're just starting out. The string passed to sscanf as the third parameter can contain multiple formatting codes, and then the string whose address you pass it as the third parameter should have text describing numeric values for each code in the format string.

The whole process looks like this:

mov ebx,[ebp+12] ; Put pointer to argument table into ebx

push dword linecount ; Push address of line count integer for sscanf push dword intformat ; Push address of integer formatting code

push dword [ebx+4]

; Push pointer to arg(1)

arg(1)

to an integer

call sscanf

; Call sscanf to convert

add

esp,12

; Clean up the

stack

we got

a number

cmp

eax,1

; Return value

of 1 says

je chkdata

; If we got a number, go on; else abort

Assuming the user entered at least one argument on the command line (and the program has already verified this), a pointer to that first argument is located at an offset of 4 from the beginning of the command-line argument pointer table. (The very first element in the table, which we call arg(0), points to the name of the program as the user typed it on the command line.) That's why we push the contents of location [EBX+4] onto the stack; we had already loaded EBX with the address of the argument pointer table. What's located at [EBX+4] is the pointer to arg(1), the first command-line argument. Refer to Figure 13.3 if this is still fuzzy.

Creating and Opening Files

By this time you should be pretty comfortable with the general mechanism for making C library calls from assembly. And whether you realize it or not, you're already pretty comfortable with some of the machinery for manipulating text files. You've already used printf to display formatted text to the screen by way of standard output. The very same mechanism is used to write formatted text to disk-based text files-you're basically substituting a real disk file for standard output. So, understanding text file I/O shouldn't be much of a conceptual leap. But unlike standard output, which is predefined for you by the C library and always available, you have to create or open a disk-based text file in order to use it. The fopen function is what does the job.

There are three general ways to open a file: for reading, for writing, and for appending. When you open a file for reading, you can read text from it via such functions as fgets, but you can't write to the file. When you open a file for writing, whatever may have been in the file before is thrown away, and new material is written at the beginning of the file. When you open a file for appending, you may write to the file, but new material is written after any existing material, and whatever was originally in the file is retained.

Ordinarily, when you open a file for writing you can't read from it, but there are special modes that allow both reading from and writing to a file. For text files especially (which are what we're speaking of here) that introduces some complications, so for the most part, text files are opened for either reading or for writing, but not both at once.

In the Unix file system, if you open a file for either writing or appending and the file does not already exist, the file is created. If you don't know if a file exists and you need to find out, attempt to open it for reading and not for writing, or you'll get the file whether it exists or not!

To use fopen, you must push the following parameters onto the stack before the call:

1.First onto the stack is a pointer to a code indicating which mode the file should be opened for. The various available modes are listed in Table 13.4. The ones you'll typically use for text files are "r," "w," and "a." These should be defined as short character strings, followed by a null:

writecode db 'w',0 opencode db 'r',0

Table 13.4: File Access Codes for Use with fopen

 

 

 

 

 

 

CODE

 

 

 

 

SENSE

 

DESCRIPTION

 

 

 

 

 

 

 

"r"

 

Opens an existing text file for reading

 

 

 

 

 

 

 

"w"

 

Creates a new text file, or opens and truncates an existing file

 

 

 

 

 

 

 

"a"

 

Creates a new text file, or opens an existing file so that new text is added at the

 

 

 

 

end

 

 

 

 

 

 

 

"r+"

 

Opens an existing text file for either writing or reading

 

 

 

 

 

 

 

"w+"

 

Creates a new text file, or opens and truncates an existing file for both read and

 

 

 

 

write access

 

 

 

 

 

 

 

"a+"

 

Creates a new text file, or opens an existing file for reading or for writing so that

 

 

 

 

new text may be added at the end

 

 

 

 

 

 

2. Next onto the stack is the address of the character string containing the name of the file to be opened.

With those two items on the stack, you make the call to fopen. If the file was successfully opened, fopen returns a file handle in EAX. If the open was unsuccessful, EAX will contain 0. Here's how opening a file for reading looks in code:

push dword opencode ; Push pointer to open-for-read code "r"

push ebx

; Pointer to name of help file is passed in ebx

call fopen

;

Attempt to open the file for reading

failed

cmp eax,0

;

fopen returns null if attempted open

<jump as needed>

 

 

 

The process to create a file and then write to it is identical, except that you must push the "w" code onto the stack instead of the "r" code.

Reading Text from Files with fgets

When fopen successfully creates or opens a file for you, it returns a file handle in EAX. Keep that file handle safe somewhere-I recommend either copying it to a memory variable allocated for that purpose or putting it in one of the sacred registers. If you store it in EAX, ECX, or EDX and then make a call to almost any C library function, the file handle in the register will be trashed and you'll lose it.

Once a file is opened for reading, you can read text lines from it sequentially with the fgets function. Each time you call fgets on an opened text file, it will read one line of the file, which is defined as all the characters up to the next newline character, which in the Unix world always indicates the end of a text line.

Now, in any given file there's no way of knowing how many characters there will be until the next newline, so it would be dangerous to just turn fgets loose to bring back characters until it encounters a newline. If you attempt to open the wrong kind of file (a binary code file is one possibility, or a compressed data file), you might bring in thousands of bytes before encountering the binary 10H value that the file system considers a newline. Whatever buffer you had allocated to hold the incoming text would overflow and fgets would perhaps destroy adjacent data or crash your program.

For that reason, you must also pass a limit value to fgets. When it begins reading a line, fgets keeps track of how many characters it has brought in from the file, and when it gets to one short of the limit value, it stops reading characters. It then adds a newline to the buffer for the final character and returns.

Set up calls to fgets this way:

1.First, push the file handle onto the stack.

2.Next, push the character count limit value. This must be the actual integer value, and not a pointer to the value!

3.

2.

3.Finally, push the address of the character buffer into which fgets should store the characters that it reads from the file.

With all that done, call fgets. If fgets returns a 0 in EAX, then you've either reached the end of the file, or else a file error happened during the read. Either way, there's no more data forthcoming from the file. But without a 0 coming back in EAX, you can assume that valid text is present in the buffer at the address you passed to fgets on the stack.

I used fgets to create a simple disk-based help system for TEXTFILE. ASM. When the user enters no command-line arguments at all, TEXTFILE reads a short text file from disk and displays it to standard output. This is a common and courteous thing to do with command-line programs, and I recommend that all utilities you build for everyday use work this way.

The code for the help system is relatively simple and demonstrates both fopen and fgets:

diskhelp:

push dword opencode ; Push pointer to open-for-read code "r"

push ebx

; Pointer to name of help file is passed in ebx

call fopen

; Attempt to open the file for reading

cmp eax,0

; fopen returns null if attempted open failed

jne .disk

; Read help info from disk, else from memory

call memhelp

 

ret

; Save handle of opened file in ebx

.disk: mov ebx,eax

.rdln: push ebx

; Push file handle on the stack

push dword HELPLEN

; Limit line length of text read

push dword helpline ; Push address of help text line buffer

call fgets

; Read a line of text from the file

add esp,12

; Clean up the stack

cmp eax,0

; A returned null indicates error or EOF

je .done

; If we get 0 in eax, close up & return

push dword helpline ; Push address of help line on the stack

call printf

; Call printf to display help line

add esp,4

; Clean up the stack

jmp .rdln

 

.done: push ebx

; Push the handle of the file to be closed

call fclose

; Closes the file whose handle is on the stack

add esp,4

; Clean up the stack

ret

; Go home

When subroutine diskhelp is called, the caller passes a pointer to the name of the help file to be read in EBX. The file is first opened. If the attempt to open the help file fails, a very short "fail safe" help message is displayed from strings stored in the [.data] section of the program. (This is the call to memhelp, which is another short subroutine in TEXT-FILE.ASM.) Never leave the user staring at a mute cursor, wondering what's going on!

Once the help file is opened, we start looping through a sequence that reads text lines from the opened file with fgets, and then writes those lines to standard output with printf. The maximum length of the lines to be read is defined by the equate HELPLEN. (As a convention, things in a program defined as macros or equates are named in uppercase letters.) Pushing an equate value on the stack is no different from pushing an immediate value, and that's how the instruction is encoded. But instead of being specified (perhaps differently) at several places all over your source code, the maximum length of your help file lines is defined in only one place and may be changed by changing that one equate only. Equates are good. Use them whenever you can.

Each time a line is read from the file, the address of the line is pushed onto the stack and displayed with printf. When no more lines are available to be read in the help file, fgets returns a 0 in EAX, and the program branches to the function call that closes the file.

Note the fclose function, which is quite simple: You push the file handle of the open file onto the stack, and

call fclose. That's all it takes to close a file!

Writing Text to Files with fprintf

Earlier in this chapter, I explained how to write formatted text to the display by way of standard output, using the printf function. The C library provides a function that writes the very same formatted text to any opened text file. The fprintf function does exactly what printf does, but it takes one additional parameter on the stack: the file handle of an opened text file. The same text stream that printf would send to standard output is sent by fprintf to that opened file.

So I won't bother reexplaining how to format text for printf using formatting codes and base strings. It's done the same way, with the same codes. Instead, I'll simply summarize how to set up a call to fprintf:

1.First push any values or pointers to values (as appropriate) onto the stack. There's no difference here from the way it's done for a call to printf.

2.Next push the base string containing the formatting codes. Again, just as for printf.

3.Finally (and here's where fprintf differs from printf), push the file handle of the file to which the text should be written.

Then call fprintf. Your text will be written to the open file. Note that to use fprintf, the destination file must have been opened for either writing or appending. If you attempt to use fprintf on a file opened for reading, you will generate an error and fprintf will return without writing any data.

An error code is returned in EAX. However, unlike the other functions we've discussed, the error code is a negative number, not 0! So, although you should compare the returned value against 0, you actually need to jump on a value less than 0-rather than 0 itself. Typically, to jump on an fprintf error condition, you would use

JL (Jump if Less), which will jump on a value less than 0.

Here's the fprintf call from TEXTFILE.ASM:

mov edi,[linecount]

; The number of lines to be filled is in edi

push esi

; esi is the pointer to the line of text

push dword 1

; The first line number

push dword writebase ; Push address of the base string

push ebx

; Push

the file handle of the open file

writeline:

; Has the line count gone to 0?

cmp dword edi,0

je donewrite

;

If so, go down & clean up stack

call fprintf

;

Write the text line to the file

dec edi

;

Decrement the count of lines to be written

add dword [esp+8],1 ; Update the line number on the stack

jmp writeline

;

Loop back and do it again

donewrite:

;

Clean up stack after call to fprintf

add esp,16

The call to fprintf is a pretty minor part of this. But there's still something very interesting to see here: The code doesn't clean up the stack immediately after the call to fprintf. In every other case of a call to a C library function, I have adjusted the stack to remove parameters immediately after the function call. What's different here?

This part of the code from TEXTFILE.ASM writes a single text line to the output file repeatedly, for a number of times specified in the memory variable linecount. Instead of wasting time pushing and removing the parameters for every write to the file, I waited until all the calls to fprintf were finished, and only then (at the label donewrite) cleaned up the stack.

But that leaves the question of changing the line number value for each write. TEXTFILE writes an initial line number before each line of text written to the file, and that number changes for each line. But instead of pushing a new line number value for each call to fprintf (which would require removing and repushing everything else, too), reach right into the stack and update the value that has been pushed on the stack, after each call to fprintf:

add dword [esp+8],1 ; Update the line number on the stack

I counted the number of bytes in each of the parameters passed to fprintf, and worked out where the pushed line number value was on the stack. In this case (and it may change depending on how many values you pass to fprintf) it was 8 bytes higher on the stack than the position of the stack pointer ESP.

There's nothing dicey about changing parameters that have already been pushed onto the stack, especially if it can save you a whole bunch of pushing and popping. Just make sure you know where the things are that you want to change! Needless to say, attempting to update a counter but changing an address instead can lead to a quick crash. This is assembly, guys. Your cushy chair is gone.

Gathering Your Subroutines into Libraries

Just as with DOS, you can build your own libraries of subroutines that you develop and use them in all your programs. Here's how to go about it in general terms:

No entry-point definition or register saving has to happen. Just create a new source code file and paste the subroutine source code into the file, which must have a .ASM file extension.

List all of the callable entry points to all subroutines, as well as any other identifiers that may be used by other programs and libraries, as global.

If the subroutines call any C library routines, or routines in other libraries you own or have created, or use variables or other identifiers defined outside the library, declare all such external identifiers as extern.

When adding library routines to a program, update the make file for that program so that the final executable has a dependency on the library.

This last point is the only one that requires additional discussion. The following make file builds the TEXTFILE.ASM demo program, which links in a library called LINLIB.ASM. Note that there is a whole new line specifying how the object file LINLIB.O is assembled, and also that the final binary file TEXTFILE depends on both TEXTFILE.O and LINLIB.O.

Because the TEXTFILE executable depends on both TEXTFILE.O and LINLIB.O, any time you make changes to either TEXTFILE.ASM or LINLIB.ASM, the make utility will completely relink the executable file via gcc. However, unless you change both .ASM files, only the .ASM file that is changed will be assembled. The magic of make is that it does nothing that doesn't need to be done.

textfile: textfile.o linlib.o

gcc textfile.o linlib.o -o textfile textfile.o: textfile.asm

nasm -f elf textfile.asm linlib.o: linlib.asm

nasm -f elf linlib.asm

The file LINLIB.ASM is on the CD-ROM for this book. The subroutines it contains have been gathered from other programs in this chapter, so it would be repetitive to reprint them all here.

Finally, the TEXTFILE.ASM program follows, in its entirety. Make sure you can read all of it-there's nothing here I haven't covered somewhere in the book. And if you want a challenge, here's one for your next project: Expand TEXTFILE to read in a text file, and write it out again with line numbers in front of each line of text. This sort of utility is called a text filter, and it's one of the most common sorts of Unix programs there is.

; Source name

: TEXTFILE.ASM

; Executable name : TEXTFILE

; Version

: 1.0

; Created date

: 11/21/1999

; Last update

: 12/4/1999

; Author

: Jeff Duntemann

; Description

: A text file I/O demo for Linux, using NASM 0.98

;

 

;Build using these commands:

;nasm -f elf textfile.asm

;nasm -f elf linlib.asm

;gcc textfile.o linlib.o -o textfile

;Note that this program requires several subroutines in an external

;library named LINLIB.ASM.

[SECTION .text]

; Section containing code

;;These externals are all from the standard C library: extern fopen

extern fclose extern fgets extern fprintf extern printf extern sscanf extern time

;;These externals are from the associated library LINLIB.ASM:

extern seedit

; Seeds the random number generator

extern pull6

; Generates a 6-bit random number from 0-63

extern newline

; Outputs a specified number of newline chars

global main

; Required so linker can find entry point

main:

; Set up stack frame for debugger

push ebp

mov ebp,esp

; Program must preserve ebp, ebx, esi, & edi

push ebx

push esi

 

push edi

 

;;; Everything before this is boilerplate; use it for all ordinary apps!

call seedit

; Seed the random number generator

;;First test is to see if there are command line arguments at all.

;;If there are none, we show the help info as several lines. Don't

;;forget that the first arg is always the program name, so there's

;;always at least 1 command-line argument!

mov eax,[ebp+8]

; Load argument count from stack into eax

cmp eax,1

; If count is 1, there are no args

ja chkarg2

; Continue if arg count is > 1

mov ebx, dword diskhelpnm ; Put address of help file name in ebx

call diskhelp

; If only 1 arg, show help info...

jmp gohome

; ...and exit the program

;; Next we check for a numeric command line argument 1:

chkarg2:

; Put pointer to argument table into ebx

mov ebx,[ebp+12]

push dword linecount ; Push address of line count integer for sscanf

push dword

intformat ; Push address of integer formatting code

push dword

[ebx+4]

; Push pointer to arg(1)

call sscanf

 

; Call sscanf to convert arg(1) to an integer

add esp,12

 

; Clean up the stack

cmp eax,1

 

; Return value of 1 says we got a number

je chkdata

 

; If we got a number, go on; else abort

mov eax, dword err1

; Load eax with address of error message #1

call showerr

; Show the error message

jmp gohome

 

; Exit the program

;;Here we're looking to see if there are more arguments. If there

;;are, we concatenate them into a single string no more than BUFSIZE

;;chars in size. (Yes, I *know* this does what strncat does...)

chkdata:

; Is there a second argument?

cmp dword [ebp+8],3

jae getlns

; If so, we have text to fill a file with

call randline

; If not, generate a line of random text

jmp genfile

; Note that randline returns ptr to line in esi

; Go on to create the file

;;Here we copy as much command line text as we have, up to BUFSIZE

;;chars, into the line buffer buff. We skip the first two args

;;(which at this point we know exist) but we know we have at least

;;one text arg in arg(2). Going into this section, we know that

;;ebx contains the pointer to the arg table. All other bets are off.

getlns: mov edx,2

; We know we have at least arg(2), start there

mov edi,dword buff

;

Destination pointer is start of char buffer

xor eax,eax

;

Clear

eax to 0 for the character counter

cld

; Clear

direction flag for up-memory movsb

grab: mov esi,[ebx+edx*4] ; Copy pointer to next arg into esi

.copy: cmp byte [esi],0 ; Have we found the end of the arg?

je .next

;

If so, bounce to the next arg

movsb

;

Copy char from [esi] to [edi]; inc edi & esi

inc

eax

; Increment total character count

cmp

eax,BUFSIZE

; See if we've filled the buffer to max count

je addnul

; If so, go add a null to buff & we're done

jmp .copy

 

 

.next: mov byte [edi],' '

; Copy space to buff to separate args

inc edi

;

Increment destination pointer for space

inc eax

; Add one to character count too

cmp eax,BUFSIZE

;

See if we've now filled buff

je addnul

; If so, go down to add a null and we're done

inc edx

;

Otherwise, increment the argument count

cmp edx, dword [ebp+8]

; Compare against argument count

jae addnul

; If edx = arg count, we're done

jmp grab

;

And go back and copy it

addnul: mov byte [edi],0 ; Tuck a null on the end of buff

mov esi, dword buff

;

File write code expects ptr to text in esi

;; Now we create a file to fill with the text we have:

genfile:

push dword writecode ; Push pointer to file write/create code ('w')

push dword newfilename

; Push pointer to new file name

call fopen

;

Create/open file

add esp,8

;

Clean up the stack

mov ebx,eax

; eax contains the file handle; save in ebx

;; File is open. Now let's fill it with text:

mov edi,[linecount]

; The number of lines to be filled is in edi

push esi

; esi is the pointer to the line of text

push dword 1

;

The first line number

push dword writebase ;

Push address of the base string

push ebx

; Push the file handle of the open file

writeline:

; Has the line count gone to 0?

cmp dword edi,0

je donewrite

; If so, go down & clean up stack

call fprintf

; Write the text line to the file

dec edi

; Decrement the count of lines to be written

add dword [esp+8],1

; Update the line number on the stack

jmp writeline

; Loop back and do it again

donewrite:

 

 

; Destroy stack frame before returning
; Return control to to the C shutdown code

add esp,16

; Clean up stack after call to fprintf

;; We're done writing text; now let's close the file:

closeit:

; Push the handle of the file to be closed

push ebx

call fclose

; Closes the file whose handle is on the stack

add esp,4

 

;;; Everything after this is boilerplate; use it for all ordinary apps! gohome: pop edi ; Restore saved registers

pop esi pop ebx

mov esp,ebp pop ebp

ret

;;;

SUBROUTINES================================================================

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

;Disk-based mini-help subroutine -- Last update 12/5/1999

;This routine reads text from a text file, the name of which is passed by

;way of a pointer to the name string in ebx. The routine opens the text file

;reads the text from it, and displays it to standard output. If the file

;cannot be opened, a very short memory-based message is displayed instead. ;---------------------------------------------------------------

diskhelp:

; Push pointer to open-for-read code "r"

push dword opencode

push ebx

; Pointer to name of help file is passed in ebx

call fopen

; Attempt to open the file for reading

add

esp,8

; Clean up the stack

cmp eax,0

; fopen returns null if attempted open failed

jne .disk

; Read help info from disk, else from memory

call memhelp

 

ret

 

; Save handle of opened file in ebx

.disk: mov ebx,eax

.rdln: push ebx

; Push file handle on the stack

push dword HELPLEN

; Limit line length of text read

push dword helpline

; Push address of help text line buffer

call fgets

; Read a line of text from the file

add esp,12

; Clean up the stack

cmp eax,0

; A returned null indicates error or EOF

jle .done

; If we get 0 in eax, close up & return

push dword helpline

; Push address of help line on the stack

call printf

; Call printf to display help line

add esp,4

; Clean up the stack

jmp .rdln

 

.done: push ebx

; Push the handle of the file to be closed

call fclose

; Closes the file whose handle is on the stack

add esp,4

; Clean up the stack

ret

 

; Go home

memhelp:

mov eax,1 call newline

mov ebx, dword helpmsg ; Load address of help text into eax

.chkln: cmp dword [ebx],0 ;

Does help msg pointer point to a null?

jne

.show

;

If not, show the help lines

mov

eax,1

; Load eax with number of newslines to output

call newline

; Output the newlines

ret

; If yes, go home

.show: push ebx

; Push address of help line on the stack

call printf

; Display the line

add esp,4

; Clean up the stack

add ebx,HELPSIZE

; Increment address by length of help line

jmp .chkln

; Loop back and check to see if we done yet

showerr:

; On entry, eax contains address of error message

push eax

call printf

; Show the error message

add esp,4

; Clean up the stack

ret

; Go home; no returned values

randline:

; BUFSIZE tells us how many chars to pull

mov ebx, BUFSIZE

mov byte [buff+BUFSIZE+1],0 ; Put a null at the end of the buffer first

.loop: dec ebx

; BUFSIZE is 1-based, so decrement

call pull6

; Go get a random number from 0-63

mov cl,[chartbl+eax] ; Use random # in eax as offset into table

mov [buff+ebx],cl

; and copy character from table into cl

; Copy char from cl to character buffer

cmp ebx,0

; Are we done having fun yet?

jne .loop

; If not, go back and pull another

mov esi, dword buff

; Copy address of the buffer into esi

ret

 

; and go home

[SECTION .data]

; Section containing initialized data

intformat

dd '%d',0

 

writebase

db 'Line #%d: %s',10,0

newfilename db 'testeroo.txt',0

diskhelpnm db 'helptextfile.txt',0

writecode

db 'w',0

 

opencode

db 'r',0

 

chartbl

db '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-@

err1

db 'ERROR: The first command line argument must be an integer!',10,0

helpmsg

db 'TEXTTEST: Generates a test file. Arg(1) should be the # of ',10,

HELPSIZE EQU $-helpmsg

 

 

db 'lines to write to the file. All other args are concatenated',10,

 

db 'into a single line and written to the file. If no text args',10,

 

db 'are entered, random text is written to the file. This msg ',10,0

helpend

db 'appears only if the file HELPTEXTFILE.TXT cannot be opened. ',10

dd 0

 

[SECTION .bss]

; Section containing uninitialized data

linecount resd 1

; Reserve integer to hold line count

HELPLEN

EQU 72

; Define length of a line of help text data

helpline

resb HELPLEN

; Reserve space for disk-based help text line

BUFSIZE

EQU 64

; Define length of text line buffer buff

buff

resb BUFSIZE+5 ; Reserve space for a line of text.