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

Using BIOS Services

In the last chapter we looked closely at DOS services, which are accessed through the DOS services dispatcher. The DOS dispatcher lives at the other end of software interrupt 21H and offers a tremendous list of services at the disposal of your programs. There's another provider of services in your machine that lives even deeper than DOS: the ROM BIOS. ROM is an acronym for read-only memory, and it indicates memory chips whose contents are burned into their silicon and do not vanish when power is turned off. BIOS is an acronym for Basic Input/Output System, and it is just that: a collection of fundamental routines for dealing with your computer's input and output peripherals. These include disk drives, displays, printers, and the like. DOS uses BIOS services as part of some of the services that it provides.

Like DOS, BIOS services are accessed through software interrupts. Unlike DOS, which channels nearly all requests for its services through the single interrupt 21H, BIOS uses numerous interrupts (about 10) and groups similar categories of services beneath the control of different interrupts. For example, video display services are accessed through interrupt 10H, keyboard services come through interrupt 16H, printer services through interrupt 17H, and so on.

The overall method for using BIOS services, however, is very similar to that of DOS. You load a service number and sometimes other initial values into the registers and then execute an INT <n> instruction, where the n depends on the category of services you're requesting.

Nothing difficult about that at all. Let's start building some tools.

Positioning the Hardware Cursor

So far, in writing to the display, we've simply let the text fall where it may. In general this means one line of text following another, and when the screen fills, DOS scrolls the screen upward to make room on the bottom line for more text. This makes for dull programs, very similar to programming in the bad old days when everything was done on clunky mechanical printers called Teletypes. (Indeed, this kind of screen I/O is called "glass Teletype" I/O, due to its similarity to a printer scrolling paper up one line at a time.)

Let's leave the glass teletypes behind and take full control of the cursor. BIOS service 10H (often nicknamed VIDEO, in uppercase, for reasons that are obscure) offers a simple service to position the hardware cursor on the text screen. The service number is loaded into AH. This is a common thread through all BIOS services: The service number is placed into AH. A 0 must be placed in BH unless you intend to tinker with multiple text display pages. That's a story for another time (and not an especially useful feature in the twenty-first century), so while you're learning, assume BH should be set to 0 for cursor positioning.

The new position of the cursor must be loaded into the two halves of the DX register. Cursor positions are given as X,Y coordinate pairs. The X component of the cursor position is the number of character columns to the right of the left margin where we want the cursor to be. The Y component is the number of lines down from the top of the screen where we want the cursor to be. The X component is loaded into DL, and the Y component is loaded into DH. The routine itself is nothing more than this:

GotoXY:

; Select VIDEO service 2: Position cursor

mov AH,02H

mov BH,0

; Stay with display page 0

int 10H

; Call VIDEO

ret

; Return to the caller

Don't forget that the X and Y values must be loaded into DX by the caller (and that means you!). Using

GotoXY is done this way:

mov DX,[TextPos] ; TextPos contains X,Y position values

call GotoXY

; Position cursor

mov DX,EatMsg1

; Load offset of EatMsg1 string into DX

call Write

; and display it

EAT3.ASM uses GotoXY to position the cursor, but it does something else as well: It clears the display. If you're going to be moving the cursor at will around the screen with GotoXY, it makes sense to start with a completely empty screen so that the remains of earlier programs and DOS commands don't clutter up the view.

There's another VIDEO service that can do the job. Service 6 is an interesting and powerful one: Not only does it clear the screen, it can scroll the screen as well, by any specified number of lines. Furthermore, it can clear or scroll the entire screen, or only a rectangular portion of the screen, leaving the rest of the screen undisturbed.

If the term scrolling is unfamiliar to you, just press Enter repeatedly at the DOS prompt and watch what happens when you reach the bottom line of the screen. The displayed text on the screen jumps up by one line, and an empty line appears at the bottom of the screen. The DOS prompt is then redisplayed in the empty line. Scrolling is the process of making the screen jump up by one or more lines and inserting one or more blank lines at the bottom as appropriate.

Using VIDEO Service 6

Understanding VIDEO service 6 involves a fair number of values that need to be passed to the service in registers. The one unchanging item is the service number itself, passed as 6 in (as with all BIOS services) register AH.

Service 6 acts upon a rectangular region of the display. This may be the full screen or it may be only part of the screen. You must pass the coordinates of the upper-left and lower-right corners of the region you want to work on in registers CX and DX. Because screen coordinates are always smaller than 255 (which is the largest value that can be expressed in 8 bits), the register halves of CX and DX are used independently to carry the X and Y values.

The upper-left corner's X coordinate is passed in CL, and the upper-left corner's Y coordinate is passed in CH. These are zero-based coordinates, meaning that they count from 0 rather than 1. Confusion is possible here because most high-level languages such as Borland Pascal number coordinates on the screen from 1. In other words, the upper-left corner of the screen in Borland Pascal is given by the coordinates 1,1. To the BIOS, however, that same corner of the screen is 0,0. The width and height values of a typical screen in Borland Pascal would be 80 × 25; the BIOS would say 79 × 24.

Similarly, the lower-right corner's X coordinate is passed in DL, and the lower-right corner's Y coordinate is passed in DH. (Again, counting from 0.)

Service 6 either scrolls or clears the region. It can scroll the screen upward by any arbitrary number of lines. This number is passed to service 6 in register AL. Clearing the region is a special case of scrolling it: When you specify that 0 lines be scrolled, the entire region is cleared instead.

The full screen is actually a special case of a rectangular region. By passing the coordinates of the upper-left and lower-right corners of the screen (0,0 and 79,24), the full screen is cleared.

Procedures with Multiple Entry Points

This is a lot of versatility for one service to handle, and it brings up a couple of questions. First of all, how versatile should a single procedure be? Should there be one procedure to clear the whole screen, another procedure to clear part of a screen, and a third procedure to scroll part of the screen?

The answer is that one procedure can do all three, and not duplicate any code at all. The method involves writing a single procedure that has four different entry points. Each entry point is a label, which may be called with a CALL instruction. When a given entry point's label is called, execution begins at the instruction specified by that label. There is only one RET instruction, so the procedure is in fact one procedure. It's like a house with three front doors but only one back door; having three front doors does not make it three separate houses.

Here's what such a creature might look like:

ClrScr:

mov CX,0

; Upper left corner of full screen

mov DX,LRXY

; Load lower-right XY coordinates into DX

ClrWin:

; 0 specifies clear entire region

mov AL,0

ScrlWin:

; Specify "normal" attribute for blanked line(s)

mov BH,07H

VIDEO6:

; Select VIDEO service 6: Initialize/Scroll

mov AH,06H

int 10H

; Call VIDEO

ret

; Return to the caller

There's nothing much to this. What we have here is a collection of MOV instructions which set up values in registers before calling VIDEO through interrupt 10H. Note that all of the entry points must be given as valid labels with colons.

The multiple entry points exist only to allow you to skip certain portions of the procedure that set up values that you don't want set. All the registers used by VIDEO service 6 must be set up somewhere. However, they can either be set within the procedure or in the caller's code just before the procedure is called. If the procedure sets them, they have to be set to some generally useful configuration (say, clearing the entire screen), whereas if the caller sets them, the registers can be set to serve the caller's needs and make service 6 perform any of its varied combinations.

So it is with procedure ClrScr. If you enter ClrScr through its main or top entry point, all of its internal code will be executed. CX and DX will be set to the upper-left and lower-right corner coordinates of the full screen, AL is set to 0 to clear the full screen rather than scroll it, and BH is loaded with the "normal" (that is, blank, for white text on a black background) text display attribute. Then service 6 is called.

If you wish to clear only a rectangular area of the screen (a window.), you would use the ClrWin entry point. This entry point starts executing the code after CX and DX are set to the corners of the full screen. This means that the caller must load CX and DX with the upper-left and lower-right corners of the screen region to be cleared. Calling ClrWin without setting CX and DX at all will execute service 6 with whatever leftover garbage values happen to be in CX and DX. Something will happen, for certain. Whether it's something that you want to happen is far less certain.

Keeping in mind that for proper operation, all of service 6's required registers must be set, calling

ClrWin would be done this way:

mov CX,0422H ; Set upper left corner to X=22H; Y=04H mov DX,093AH ; Set lower right corner to X=3AH; Y=09H call ClrWin ; Call the window-clear procedure

The two MOV instructions are worth a closer look. Rather than use a separate instruction to load each half of DX and CX, the two halves are loaded together by loading a 16-bit immediate data value into the full 16-bit register. Thus, two MOV instructions can do the work that a first glance might think would take four MOV instructions. This is a good example of writing tight, efficient assembler code. The trick is to document it (as I've done in the preceding) to make sure you understand six weeks from now what the magic number 093AH really means!

The first instruction at the label ClrWin sets AL to 0. Setting AL to 0 indicates that the region is to be cleared, not scrolled. If, in fact, you do want to scroll the region, you need to skip the MOV instruction that loads 0 into AL. This is the purpose of the entry point labeled ScrlWin. It gets you into the procedure below the point at which you select clearing over scrolling. This means that you not only have to set the corners of the region to be scrolled, but also the number of lines to scroll as well:

mov CX, 0422H ; Set upper left corner to X=22H; Y=04H mov DX, 093AH ; Set lower right corner to X=3AH; Y=09H mov AL, 1 ; Set to scroll by one line

call ScrlWin ; Call the window-scroll procedure

As you can see, more and more of the work is being done by caller and less and less within the procedure.

Note that there is no entry point to scroll the full screen. To scroll the full screen, you need to load the coordinates of the corners of the full screen into CX and DX, and then call ClrWin as though you were clearing just a portion of the screen. If you do a lot of screen scrolling, you might define a separate routine for scrolling the full screen. As an interesting exercise, write such a routine and a program to test it.

As one more entry point, I included a label VIDEO6, which short-circuits all of the register setup apart from loading the service number itself into AH. This allows you to do something odd and infrequently done, such as scrolling the entire screen by three lines.

Memory Data or Immediate Data?

You may be wondering what the variable identifier LRXY is for and where it is defined. What it's for is simply to hold the current X, Y coordinates for the lower-right corner of the screen. Where it's defined is in the program's data segment, in the usual way variables are defined, as you'll see if you look ahead to the full listing of EAT3.ASM which follows.

The more interesting question is why. Most of the time I've been showing you values loaded into registers from immediate data, and this is often useful. The coordinates of the upper-left corner of the full screen, for example, are always going to be 0,0, and nothing will ever change that. The lower-right corner, however, is not necessarily always 79,24.

The original 1981 vintage IBM MDA and CGA graphics adapters are indeed capable of 80 by 25 text screens and no more. However, with an EGA, it is possible to have an 80 by either 25 or 43 text screen, and the VGA introduced in 1987 with the PS/2 line can display either 25or 50-line screens, all 80 characters wide. The newer super VGA video boards are capable of even more different text modes, some of them with more than 80 characters in a visible line. If your program can determine what size screen is in force when it is invoked, it can modify its displays accordingly.

Avoid dropping immediate values into code (we call this hard coding) whenever you can. A better strategy, which I follow from now on, uses variables in the data segment initialized with currently correct values when the program begins running.

Use Comment Headers!

As time goes on, you'll find yourself creating dozens or even hundreds of procedures as a means of not reinventing the same old wheel. The libraries of available procedures that most high-level language vendors supply with their compilers just don't exist with assembly language. By and large, you create your own.

Keeping such a list of routines straight is no easy task when you've written them all yourself. You must document the essential facts about each individual procedure or you'll forget them, or remember them incorrectly and act on bad information. (The resultant bugs are often devilishly hard to find because you're sure you remember everything there is to know about that proc! After all, you wrote it!)

I recommend adding a comment header to every procedure you write, no matter how simple. Such a header should contain the following information:

The name of the procedure

The date it was last modified

What it does

What data items the caller must pass it to make it work correctly

What data is returned by the procedure, if any, and where it is returned (for example, in register CX)

What other procedures, if any, are called by the procedure

Any "gotchas" that need to be kept in mind while writing code that uses the procedure

A typical workable procedure header is this:

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

;WRITELN -- Displays information to the screen via DOS

;

service 9 and issues a newline

; Last update 9/11/99

;

;1 entry point:

;Writeln:

;Caller must pass:

;DS: The segment of the string to be displayed

;DX: The offset of the string to be displayed

;String must be terminated by "$"

;Action: Displays the string at DS:DX up to the "$" marker

;marker, then issues a newline. Hardware cursor

;will move to the left margin of the following

;line. If the display is to the bottom screen

;line, the screen will scroll.

;Calls: Write

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

A comment header does not relieve you of the responsibility of commenting the individual lines of code within the procedure. It's a good idea to put a short comment to the right of every line that contains a machine instruction mnemonic, and also (in longer procedures) a comment block describing every major functional block within the procedure.

A program written to make use of procedures to control the screen follows. Examine EAT3.ASM, and notice the various commenting conventions. For a very short program such as this, such elaborate internal documentation might seem overkill. Once your programs get serious, however, you'll be very glad you expended the effort.

; Source name

: EAT3.ASM

; Executable name : EAT3.COM

; Code model

: Real mode flat model

; Version

: 1.0

; Created date

: 7/31/1999

; Last update

: 9/11/1999

; Author

: Jeff Duntemann

; Description

: A DOS .COM file demonstrating the use of software

;

interrupts to control the text mode display through

;

calls into BIOS VIDEO interrupt 10H. Assemble with

;

NASM 0.98.

[BITS 16]

; Set 16 bit code generation

[ORG 0×0100]

; Set code start address to 100h (.COM file)

[SECTION .text] ; Section containing code

Start:

; Clear the full display

call ClrScr

;Make sure you understand the difference between

;MOV DX,Identifier and MOV DX,[Identifier] !!!

mov word [TextPos],0914H ; 0914H = X @ 20, Y @ 9

mov DX,[TextPos] ; TextPos contains X,Y position values

call GotoXY

; Position cursor

mov DX,EatMsg1

; Load offset of EatMsg1 string into DX

call Write

; and display it

 

mov DX,[TextPos] ; Re - use text position variable

 

mov DH,10

; Put new Y value into DH but re-use X

 

call GotoXY

; Position

cursor

 

mov DX,EatMsg2

; Load offset of EatMsg2 string into DX

 

call Write

;

and display it

 

mov DX,1701H

; Move cursor to bottom left corner of screen

 

call GotoXY

;

so that

'Press enter...' msg is out of the way.

 

mov ax,4C00H

; This function exits the program

 

int 21H

;

and returns control to DOS.

;

-----------------------------PROCEDURE SECTION

|

;

|

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

 

 

 

|

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

GOTOXY - Positions the hardware cursor to X,Y

;

;Last update 7/31/99

;1 entry point:

;

GotoXY:

 

;

 

;

Caller must pass:

;

DL: X

value

These are both 0-based; i.e., they

;

DH: Y

value

assume a screen 24 by 79, not 25 by 80

;Action: Moves the hardware cursor to the X,Y position

;loaded into DL and H.

;

---------------------------------------------------------------

 

GotoXY:

; Select VIDEO service 2: Position cursor

 

mov AH,02H

 

mov BH,0

; Stay with display page 0

 

int 10H

; Call VIDEO

 

ret

; Return to the caller

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

CLRSCR - Clears or scrolls screens or windows

;

;

Last update 3/5/89

;

 

 

;4 entry points:

;ClrScr:

;No values expected from caller

;Action: Clears the entire screen to blanks with 07H as

;the display attribute

;

;ClrWin:

;Caller must pass:

;CH: Y coordinate, upper left corner of window

;CL: X coordinate, upper left corner of window

;DH: Y coordinate, lower right corner of window

;DL: X coordinate, lower right corner of window

;Action: Clears the window specified by the caller to

;blanks with 07H as the display attribute

;

;ScrlWin:

;Caller must pass:

;CH: Y coordinate, upper left corner of window

;CL: X coordinate, upper left corner of window

;DH: Y coordinate, lower right corner of window

;DL: X coordinate, lower right corner of window

;AL: number of lines to scroll window by (0 clears it)

;Action: Scrolls the window specified by the caller by

;the number of lines passed in AL. The blank

;lines inserted at screen bottom are cleared

;to blanks with 07H as the display attribute

;

VIDEO6:

 

;

 

;

Caller must pass:

;

CH: Y coordinate, upper left corner of window

;

CL: X coordinate, upper left corner of window

;

DH: Y coordinate, lower right corner of window

;

DL: X coordinate, lower right corner of window

;

AL: number of lines to scroll window by (0 clears it)

;

BH: display attribute for blanked lines (07H is "normal")

;

Action: Generic access to BIOS VIDEO service 6. Caller

;

must pass ALL register parameters as shown above

;

---------------------------------------------------------------

 

ClrScr:

; Upper left corner of full screen

 

mov CX,0

 

mov DX,LRXY

; Load lower-right XY coordinates into DX

ClrWin:

; 0 specifies clear entire region

 

mov AL,0

ScrlWin:

; Specify "normal" attribute for blanked line(s)

 

mov BH,07H

VIDEO6:

; Select VIDEO service 6: Initialize/Scroll

 

mov AH,06H

 

int 10H

; Call VIDEO

 

ret

; Return to the caller

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

WRITE - Displays information to the screen via DOS

;

;service 9: Print String

;Last update 7/31/99

;

1 entry point:

;

;

Write:

 

;

 

;

Caller must pass:

;

DS: The segment of the string to be displayed

;

DX: The offset of the string to be displayed

;

String must be terminated by "$"

;

Action: Displays the string at DS:DX up to the "$" marker

;

---------------------------------------------------------------

 

Write:

; Select DOS service 9: Print String

 

mov AH,09H

 

int 21H

; Call DOS

 

ret

; Return to the caller

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

;WRITELN - Displays information to the screen via DOS

;service 9 and issues a newline

;Last update 7/31/99

;

;1 entry point:

;Writeln:

;Caller must pass:

;DS: The segment of the string to be displayed

;DX: The offset of the string to be displayed

;String must be terminated by "$"

;Action: Displays the string at DS:DX up to the "$" marker

;marker, then issues a newline. Hardware cursor

;will move to the left margin of the following

;line. If the display is to the bottom screen

;line, the screen will scroll.

;Calls: Write

;

---------------------------------------------------------------

 

 

Writeln:

; Display the string proper through Write

 

call Write

 

mov DX,CRLF

; Load offset of newline string to DX

 

call Write

; Display the newline string through Write

 

ret

; Return to the caller

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

DATA SECTION

|

;

|

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

 

 

|

[SECTION .data]

; Section containing initialized data

LRXY

DW

; Combined 0-based X,Y of 80 × 25 screen LR corner:

184FH ; 18H = 24D; 4FH = 79D

TextPos

DW

0 ; Memory variable to store text screen coordinates

EatMsg1

DB

"Eat at Joe's . . . ",'$'

EatMsg2

DB

"...ten million flies can't ALL be wrong!",'$'

CRLF

DB

0DH,0AH,'$'

Space

DB

" ",'$'