Chapter 2: The putstring Function
For this next program, I will be introducing the “putstring” function that I wrote. This function takes the address of wherever the ax register points to, then does a routine to scan for the next zero byte. Then it subtracts the original address from the address where the zero was found. By doing this, it knows how many bytes there are to print in the string.
Then it loads the registers in the following way:
- AH = 40h (the DOS write call)
- BX = file handle
- CX = number of bytes to write
- DS:DX -> data to write
Then interrupt 21h is called and this executes the most fun system call possible. It can print any string you give it. Take the following source and assemble it with either FASM or NASM as described in chapter 1.
1 org 100h
2
3 main:
4
5 mov ax,text
6 call putstring
7
8 mov ax,4C00h
9 int 21h
10
11 text db 'Hello World!',0Dh,0Ah,0
12
13 ;This section is for the putstring function I wrote.
14 ;It will print any zero terminated string that register ax points to
15
16 stdout dw 1 ; variable for standard output so that it can theoretically be redirected
17
18 putstring:
19
20 push ax
21 push bx
22 push cx
23 push dx
24
25 mov bx,ax ;copy ax to bx for use as index register
26
27 putstring_strlen_start: ;this loop finds the length of the string as part of the putstring function
28
29 cmp [bx], byte 0 ;compare this byte with 0
30 jz putstring_strlen_end ;if comparison was zero, jump to loop end because we have found the length
31 inc bx ;increment bx (add 1)
32 jmp putstring_strlen_start ;jump to the start of the loop and keep trying until we find a zero
33
34 putstring_strlen_end:
35
36 sub bx,ax ; sub ax from bx to get the difference for number of bytes
37 mov cx,bx ; mov bx to cx
38 mov dx,ax ; dx will have address of string to write
39
40 mov ah,40h ; select DOS function 40h write
41 mov bx,[stdout] ; file handle 1=stdout
42 int 21h ; call the DOS kernel
43
44 pop dx
45 pop cx
46 pop bx
47 pop ax
48
49 ret
If you assembled it and ran it in DOS, you should get
1 Hello World!
As the result. I know this doesn’t seem very impressive, but this program accomplishes a lot. You see, in Assembly, you don’t have access to C’s “printf” or even “puts”. However, the 40h call of DOS is useful enough that during the course of this book, I will introduce how you can use my functions to replace the standard library output functions or even modify them if you don’t like the way I wrote them!
If I had to compare DOS 40h to something in C, I would compare it to the “fwrite” function which writes a specified number of bytes to a specific file stream. Writing to file 1 is the same as writing to the screen.
Specifically, entry “D-2140” in “INTERRUP.F” of Ralf Brown’s Interrupt list is where I got my documentation I required to write the “putstring” function.
If you look at the source, you will see I included a “main:” label. This wasn’t actually necessary but I added it for clarity and to distinguish the main function from the putstring function. This is a convention I will keep for the remainder of this book.
The “Hello World!” is defined as data in the assembler like this.
1 text db 'Hello World!',0Dh,0Ah,0
That line is not actually assembly language but is pure data according to the way it is defined in both FASM and NASM. The “0Dh,0Ah” are bytes defining the end of a line in DOS. Finally, I ended the string with a 0 because the putstring function uses it to know when to stop printing.
Therefore, the entire main function is:
1 main:
2
3 mov ax,text
4 call putstring
5
6 mov ax,4C00h
7 int 21h
The “call” instruction calls a function. As far as assembly is concerned, a function is just a label the same as why you might use for a loop. The difference is that a “ret” intruction will send the program back to where it should be when the function is done. If you forget the “ret” instruction, you will cause a crash because the computer will keep trying to execute code that you did not write. Luckily, if you are running your DOS program in DOSBox, you will only crash the emulator and not your host operating system.
When I designed the putstring function, I chose ax as the register to first hold the address of the string. I did this because ‘a’ is the first letter of the alphabet and so I use it as the first argument for any of my written functions.
However, considering that the dx register is used for the data location in the DOS write call, perhaps it would have made more sense to write it that way. This is just a matter of personal taste and I mention it to show you that even assembly language allows a certain amount of personal style when writing your code.
You may have noticed the push instructions at the beginning of the putstring function and the pop instructions at the end of the function. The push and pop intructions operate the “stack”, It is a First In Last Out method of managing temporary storage.
Because we are required to use those 4 registers for the system call, we back them up and then restore them. This way, the registers retain their original value as if we had never modified them in the function. This may not seem important now, but in the following chapters, we will be printing lots of strings and numbers, so it is important that their values don’t change while we use them in integer sequence programs later on!
But all the putstring function does is print a string of text. It can’t print numbers as humans would expect to see them, at least not yet! In the next chapter, I will correct this problem by showing you a function that can print integers!
If you don’t understand the reason the programs in chapter 1 and 2 work, that’s because I am first establishing a code base which can be used to give you feedback. Without a way of printing output, we have no idea whether our code is correct!