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

Rally Round the Flags, Boys!

We haven't studied the Flags register as a whole. Flags is a veritable junk drawer of disjointed little bits of information, and it's tough (and perhaps misleading) to just sit down and describe all of them in detail at once. What I do is describe the flags as we encounter them in discussing the various instructions in this and future chapters.

Flags as a whole is a single 16-bit register buried inside the CPU. Of those 16 bits, 9 are actually used as flags in real mode on the x86. The remaining 7 bits are undefined in real mode and ignored. You can neither set them nor read them. Some of those 7 bits become defined and useful in protected mode on the 386 CPU and its successors, but their uses are fairly arcane and I won't be covering them in this book.

A flag is a single bit of information whose meaning is independent from any other bit. A bit can be set to 1 or cleared to 0 by the CPU as its needs require. The idea is to tell you, the programmer, the state of certain conditions inside the CPU, so that your program can test for and act on the states of those conditions.

I often imagine a row of country mailboxes, each with its own little red flag on the side. Each flag can be up or down, and if the Smiths' flag is up, it tells the mailman that the Smiths have placed mail in their box to be picked up. The mailman looks to see if the Smiths' flag is raised (a test) and, if so, opens the Smiths' mailbox and picks up the waiting mail.

Each of the Flags register's nine flags has a two-letter symbol by which most programmers know them. I use those symbols most of the time, and you should become familiar with them. The flags, their symbols, and brief descriptions of what they stand for follows:

OF— The Overflow flag is set when the result of an operation becomes too large to fit in the operand it originally occupied.

DF— The Direction flag is an oddball among the flags in that it tells the CPU something that you want it to know, rather than the other way around. It dictates the direction that activity moves (upmemory or down-memory) during the execution of string instructions. When DF is set, string instructions proceed from high memory toward low memory. When DF is cleared, string instructions proceed from low memory toward high memory. I take this up again when I discuss the string instructions.

IF— The Interrupt enable flag is a two-way flag. The CPU sets it under certain conditions, and you can set it yourself using the STI and CLI instructions. When IF is set, interrupts are enabled and may occur when requested. When IF is cleared, interrupts are ignored by the CPU.

TF— When set, the Trap flag allows DEBUG's Trace command to do what it does, by forcing the CPU to execute only a single instruction before calling an interrupt routine. This is not an especially useful flag for ordinary programming and I won't have anything more to say about it.

SF— The Sign flag becomes set when the result of an operation forces the operand to become negative. By negative, we only mean that the highest-order bit in the operand (the sign bit) becomes 1 during a signed arithmetic operation. Any operation that leaves the sign positive will clear SF.

ZF— The Zero flag becomes set when the results of an operation become zero. If the operand becomes some nonzero value, ZF is cleared.

AF— The Auxiliary carry flag is used only for BCD arithmetic. BCD arithmetic treats each operand byte as a pair of 4-bit "nybbles" and allows something approximating decimal (base 10) arithmetic to be done directly in the CPU hardware by using one of the BCD arithmetic instructions. These instructions are not much used anymore; I discuss BCD arithmetic only briefly later on.

PF— The Parity flag will seem instantly familiar to anyone who understands serial data communications, and utterly bizarre to anyone who doesn't. PF indicates whether the number of set (1) bits in the low-order byte of a result is even or odd. For example, if the result is 0F2H, PF will be cleared because 0F2H (11110010) contains an odd number of 1 bits. Similarly, if the result is 3AH (00111100), PF will be set because there is an even number (four) of 1 bits in the result. This flag is a carryover from the days when all computer communications were done through a serial port, for which a system of error detection called parity checking depends on knowing whether a count of set bits in a character byte is even or odd. PF has no other use and I won't be describing it further.

CF— The Carry flag is by far the most useful flag in the Flags register, and the one you will have to pay attention to most. If the result of an arithmetic or shift operation "carries out" a bit from the operand, CF becomes set. Otherwise, if nothing is carried out, CF is cleared.

Check That Reference Page!

What I call "flag etiquette" is the way a given instruction affects the flags in the Flags register. You must remember that the descriptions of the flags on the previous pages are generalizations only and are subject to specific restrictions and special cases imposed by individual instructions. Flag etiquette for individual flags varies widely from instruction to instruction, even though the sense of the flag's use may be the same in every case.

For example, some instructions that cause a zero to appear in an operand set ZF, while others do not. Sadly, there's no system to it and no easy way to keep it straight in your head. When you intend to use the flags in testing by way of conditional jump instructions (see Chapter 10), you have to check each individual instruction to see how the various flags are affected.

Flag etiquette is a highly individual matter. Check the reference for each instruction to see if it affects the flags. Assume nothing!

A simple lesson in flag etiquette involves two new instructions, INC and DEC, and yet another interesting ability of DEBUG.

Adding and Subtracting One with INC and DEC

Several x86 machine instructions come in pairs. Simplest among those are INC and DEC, which increment and decrement an operand by one, respectively.

Adding one to something or subtracting one from something are actions that happen a lot in computer programming. If you're counting the number of times a program is executing a loop, or counting bytes in a table, or doing something that advances or retreats one count at a time, INC or DEC can be very quick ways to make the actual addition or subtraction happen.

Both INC and DEC take only one operand. An error will be flagged by DEBUG or by your assembler if you try to use either INC or DEC with two operands, or without any operands.

Try both by using the Assemble command and the Trace command under DEBUG. Assemble this short program, display the registers after entering it, and then trace through it:

MOV AX,FFFF

MOV BX,002F

DEC BX

INC AX

The session should look very much like this:

- A

1980:0100 MOV AX,FFFF

1980:0103 MOV BX,002D

1980:0106 INC AX

1980:0107 DEC BX 1980:0108

- R

AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=1980 ES=1980 SS=1980 CS=1980 IP=0100 NV UP EI PL NZ NA PO NC 1980:0100 B8FFFF MOV AX,FFFF

- T

AX=FFFF BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=1980 ES=1980 SS=1980 CS=1980 IP=0103 NV UP EI PL NZ NA PO NC 1980:0103 BB2D00 MOV BX,002D

- T

AX=FFFF BX=002D CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000

DS=1980 ES=1980 SS=1980 CS=1980 IP=0106

NV UP EI PL NZ NA PO NC

1980:0106 40

INC

AX

 

- T

 

 

 

AX=0000 BX=002D CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000

DS=1980 ES=1980 SS=1980 CS=1980 IP=0107

NV UP EI PL ZR AC PE NC

1980:0107 4B

DEC

BX

 

- T

AX=0000 BX=002C CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=1980 ES=1980 SS=1980 CS=1980 IP=0108 NV UP EI PL NZ NA PO NC

1980:0108 0F POP CS

Watch what happens to the registers. Decrementing BX predictably turns the value 2DH into value 2CH. Incrementing 0FFFFH, on the other hand, rolls over the register to 0 since 0FFFFH is the largest unsigned value that can be expressed in a 16-bit register. Adding 1 to it rolls it over to zero, just as adding 1 to 99 rolls the rightmost two digits of the sum to zero in creating the number 100. The difference with INC is that there is no carry. The Carry flag is not affected by INC, so don't try to use it to perform multidigit arithmetic.

Using DEBUG to Watch the Flags

When INC rolled AX over to zero, the Carry flag was not affected, but the Zero flag (ZF) became set (that is, equal to 1). The Zero flag works that way: When the result of an operation becomes zero, ZF is almost always set.

DEC sets the flags in the same way. If you were to execute a DEC DX instruction when DX contained 1, DX would become zero and ZF would be set.

Apart from looking at a reference guide, how can you tell what flags are affected by a given instruction? DEBUG allows you to see the flags as they change, just as it lets you dump memory and examine the values in the general-purpose and segment registers. The second line of DEBUG's three-line register display contains eight cryptic symbols at its right margin. You've been seeing them, I'm sure, without having a clue as to their meaning.

Eight of the nine 8086/8088 flags are represented by two-character symbols. (The odd flag out is Trap flag TF, which is reserved for exclusive use by DEBUG itself and cannot be examined while DEBUG has control of the machine.) Unfortunately, the symbols DEBUG uses are not the same as the standard flag symbols that programmers call the flags by. The difference is that DEBUG's flag symbols do not represent the flags' names but rather the flags' values. Each flag can be set or cleared, and DEBUG displays the state of each flag by having a unique symbol for each state of each flag, for a total of 16 distinct symbols in all. The symbols' meanings are summarized in Table 7.4.

Table 7.4: DEBUG's Flag State Symbols

 

 

 

 

 

 

 

 

 

 

FLAG

 

NAME

 

SET SYMBOL

 

CLEAR SYMBOL

 

 

 

 

 

 

 

 

 

 

 

OF

 

Overflow flag

 

OV

 

NV

 

 

 

 

 

 

 

 

 

 

 

DF

 

Direction flag

 

DN

 

UP

 

 

 

 

 

 

 

 

 

 

 

IE

 

Interrupt enable flag

 

EI

 

DI

 

 

 

 

 

 

 

 

 

 

 

SF

 

Sign flag

 

NG

 

PL

 

 

 

 

 

 

 

 

 

 

 

ZF

 

Zero flag

 

ZR

 

NZ

 

 

 

 

 

 

 

 

 

 

 

AF

 

Auxiliary carry flag

 

AC

 

NA

 

 

 

 

 

 

 

 

 

 

 

PF

 

Parity flag

 

PE

 

PO

 

 

 

 

 

 

 

 

 

 

 

CF

 

Carry flag

 

CY

 

NC

 

 

 

 

 

 

 

 

 

 

The best I can say for this symbol set is that it's not obviously obscene. It is, however, nearly impossible to memorize. You'd best keep a reduced copy of this table (perhaps taped to the back of a business card) near your keyboard if you intend to watch the waving of the x86 CPU flags.

When you first run DEBUG, the flags are set to their default values, which are these:

NV UP EI PL NZ NA PO NC

You'll note that all these symbols are clear symbols except for EI, which must be set to allow interrupts to happen. Whether you are aware of it or not, interrupts are happening constantly within your PC. Each keystroke you type on the keyboard triggers an interrupt. Every 55 milliseconds, the system clock triggers an interrupt to allow the BIOS software to update the time and date values kept in memory as long as the PC has power. If you disabled interrupts for any period of time, your real-time clock would stop and your keyboard would freeze up. Needless to say, IE must be kept set nearly all the time.

Each time you execute an instruction with the Trace command, the flags display will be updated. If the instruction that was executed affected any of the flags, the appropriate symbol will be displayed over the previous symbol.

With Table 7.4 in hand, go back and examine the flags display for the four-instruction DEBUG trace shown a few pages back. The first display shows the default values for all the flags, since no instructions have been executed yet. No change appears for the second and third flags displays, because the MOV instruction affects none of the flags.

But look closely at the flags display after the INC AX instruction executes. Three of the flags have changed state: ZF has gone from NZ (clear) to ZR (set), indicating that the operand of INC went to zero as a result of the increment operation. AF has gone from NA to AC. (Let's just skip past that one; explaining what it means would be more confusing than helpful.) Parity flag PF has gone from PO to PE. This means that as a result of the increment operation, the number of bits present in the low byte of BX went from odd to even.

Finally, look at the last flags display, the one shown after the DEC BX instruction was executed. Again, ZF, AF, and PF changed. ZF went to NZ, indicating that the DEC instruction left a nonzero value in its operand. PF, moreover, went from PE to PO, indicating that the number of bits in the low byte of BX was odd after the DEC BX instruction.

One thing to keep in mind is that even when a flag doesn't change state from display to display, it was still affected by the previously executed instruction. Five out of nine flags are affected by every INC and

DEC instruction that the CPU executes. Not every DEC instruction decrements its operand down to zero, but every DEC instruction causes some value to be asserted in ZF. The same holds true for the other four affected flags: Even if the state of an affected flag doesn't change as a result of an instruction, the state is asserted, even if only reasserted to its existing value.

Thorough understanding of the flags comes with practice and dogged persistence. It's one of the more chaotic aspects of assembly language programming, but as we'll see when we get to conditional branches, flags are what make the CPU truly come alive to do our work for us.