Monday, December 22

7




Machine Instructions and Their Operands


As we said earlier, MOV copies data from a source to a destination. MOV is an extremely versatile instruction, and understanding its versatility demands a little study of this notion of source and a destination.





Source and Destination Operands


Most machine instructions, MOV included, have one or more operands. (Some instructions have no operands.) In the machine instruction MOV AX,1, there are two operands. The first is AX, and the second is the digit 1.


By convention in assembly language, the first operand belonging to a machine instruction is the destination operand. The second operand is the source operand.



With the MOV instruction, the sense of the two operands is pretty literal: The source operand is copied to the destination operand. In MOV AX,1, the source operand 1 is copied into the destination operand AX. The sense of source and destination is not nearly so literal in other instructions, but a rule of thumb is this: Whenever a machine instruction causes a new value to be generated, that new value is placed in the destination operand.


There are three different flavors of data that may be used as operands. These are memory data, register data, and immediate data. I've laid some example MOV instructions out on the dissection pad in Table 7.1 to give you a flavor for how the different types of data are specified as operands to the MOV instruction.


































Table 7.1: MOV and Its Operands


MACHINE INSTRUCTION




DESTINATION OPERAND




SOURCE OPERAND



MOV AX,



1




Source is immediate data.



MOV BX,



CX



Both are 16-bit register data.



MOV DL,



BH



Both are 8-bit register data.



MOV [BP],




DI



Destination is memory data at SS:BP.



MOV DX,



[SI]




Source is memory data at DS:SI.



MOV BX,



[ES:BX]



Source is memory data at ES:BX.



Immediate data is by far the easiest to understand. We look at it first.






Immediate Data


The MOV AX,1 machine instruction that I had you enter into DEBUG was a good example of what we call immediate data accessed through an addressing mode called immediate addressing. Immediate addressing gets its name from the fact that the item being addressed is immediate data built right into the machine instruction. The CPU does not have to go anywhere to find immediate data. It's not in a register, nor is it stored in a data segment somewhere out in memory. Immediate data is always right inside the instruction being fetched and executed.



Immediate data must be of an appropriate size for the operand. In other words, you can't move a 16-bit immediate value into an 8-bit register half such as AH or DL. Neither DEBUG nor the stand-alone assemblers will allow you to assemble an instruction like this:



  MOV CL,67EF


CL is an 8-bit register, and 67EFH is a 16-bit quantity. Won't go!


Because it's built right into a machine instruction, you might think immediate data would be quick to access. This is true only to a point: Fetching anything from memory takes more time than fetching anything from a register, and instructions are, after all, stored in memory. So, while addressing immediate data is somewhat quicker than addressing ordinary data stored in memory, neither is anywhere near as quick as simply pulling a value from a CPU register.


Also keep in mind that only the source operand may be immediate data. The destination operand is the place where data goes, not where it comes from. Since immediate data consists of literal constants (numbers such as 1, 0, or 7F2BH), trying to copy something into immediate data rather than from immediate data simply has no meaning and is always an error.






Register Data


Data stored inside a CPU register is known as register data, and accessing register data directly is an addressing mode called register addressing. Register addressing is done by simply naming the register we want to work with. Here are some entirely legal examples of register data and register addressing:



  MOV AX,BX
MOV BP,SP
MOV BL,CH
MOV ES,DX
ADD DI,AX
AND DX,SI



The last two examples point up the fact that we're not speaking only of the MOV instruction here. Register addressing happens any time data in a register is acted on directly, irrespective of what machine instruction is doing the acting.


The assembler keeps track of certain things that don't make sense, and one such situation is having a 16-bit register and an 8-bit register half within the same instruction. Such operations are not legal-after all, what would it mean to move a 2-byte source into a 1-byte destination? And while moving a 1-byte source into a 2-byte destination might seem more reasonable, the CPU does not support it and it cannot be done.


Playing with register addressing is easy using DEBUG. Bring up DEBUG and assemble the following series of instructions:



  MOV AX,67FE
MOV BX,AX
MOV CL,BH
MOV CH,BL



Now, reset the value of IP to 0100 using the R command. Then execute the four machine instructions by issuing the T command four times in a row. The session under DEBUG would look like this:



  - A
333F:0100 MOV AX,67FE
333F:0103 MOV BX,AX
333F:0105 MOV CL,BH
333F:0107 MOV CH,BL
333F:0109
- R IP
IP 0100
:0100
- R
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=333F ES=333F SS=333F CS=333F IP=0100 NV UP EI PL NZ NA PO NC
333F:0100 B8FE67 MOV AX,67FE
- T

AX=67FE BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=333F ES=333F SS=333F CS=333F IP=0103 NV UP EI PL NZ NA PO NC
333F:0103 89C3 MOV BX,AX
- T

AX=67FE BX=67FE CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=333F ES=333F SS=333F CS=333F IP=0105 NV UP EI PL NZ NA PO NC
333F:0105 88F9 MOV CL,BH
- T

AX=67FE BX=67FE CX=0067 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=333F ES=333F SS=333F CS=333F IP=0107 NV UP EI PL NZ NA PO NC
333F:0107 88DD MOV CH,BL
- T

AX=67FE BX=67FE CX=FE67 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=333F ES=333F SS=333F CS=333F IP=0109 NV UP EI PL NZ NA PO NC
333F:0109 1401 ADC AL,01


Keep in mind that the T command executes the instruction displayed in the third line of the most recent R command display. The ADC instruction in the last register display is yet another garbage instruction, and although executing this particular instruction would not cause any harm (it's just an ADC: Add with Carry), I recommend against executing random instructions just to see what happens. Executing certain jump or interrupt instructions could wipe out sectors on your hard disk or, worse, cause internal damage to DOS that would not show up until later on.



Let's recap what these four instructions accomplished. The first instruction is an example of immediate addressing: The hexadecimal value 067FEH was moved into the AX register. The second instruction used register addressing to move register data from AX into BX. Keep in mind that the way the operands are written is slightly contrary to the common-sense view of things. The destination operand comes first. Moving something from AX to BX is done by executing MOV BX,AX. Assembly language is just like that sometimes-if that were the most peculiar thing about it, I for one would be mighty grateful ...


The third instruction and fourth instruction both move data between register halves rather than full, 16-bit registers. These two instructions accomplish something interesting. Look at the last register display, and compare the value of BX and CX. By moving the value from BX into CX a byte at a time, it was possible to reverse the order of the two bytes making up BX. The high half of BX (what we sometimes call the most significant byte, or MSB, of BX) was moved into the low half of CX. Then the low half of BX (what we sometimes call the least significant byte, or LSB, of BX) was moved into the high half of CX. This is just a sample of the sorts of tricks you can play with the general-purpose registers.


Just to disabuse you of the notion that the MOV instruction should be used to exchange the two halves of a 16-bit register, let me suggest that you do the following: Before you exit DEBUG from your previous session, assemble this instruction and execute it using the T command:




  XCHG CL,CH


The XCHG instruction exchanges the values contained in its two operands. What was interchanged before is interchanged again, and the value in CX will match the values already in AX and BX. A good idea while writing your first assembly language programs is to double-check the instruction set periodically to see that what you have cobbled together with four or five instructions is not possible using a single instruction. The x86 instruction set is very good at fooling you in that regard! (One caution: Later on, you might find that cobbling something together from simple instructions might run more quickly than the same thing accomplished by a single specialized instruction, especially on the newest Pentium-class CPUs. Pentium optimization is a truly peculiar business-but we're way ahead of ourselves now in speaking of what's fast and what's not. Learn how it works first-and then we can explore how fast it is!)







Memory Data


Immediate data is built right into its own machine instruction. Register data is stored in one of the CPU's limited collection of internal registers. In contrast, memory data is stored somewhere in the megabyte vastness of real mode memory. Specifying that address is much more complicated than simply reaching into a machine instruction or naming a register.


You should recall that a memory location must be specified in two parts: a segment address, which is one of 65,536 segment slots spaced every 16 bytes in memory, and an offset address, which is the number of bytes by which the specified byte is offset from the start of the segment. Within the CPU, the segment address is kept in one of the four segment registers, while the offset address (generally just called the offset) may be in one of a select group of general-purpose registers that includes only BP, BX, SI, and DI. (Register SP is a special case and addresses data located on the stack, as I explain in Chapter 8. To pin down a single byte anywhere within real mode's megabyte of memory, you need both the segment and offset components. We generally write them together, specified with a colon to separate them, as either literal constants or register names: 0B00:0167, DS:SI or CS:IP.






BX's Hidden Agenda


One of the easiest mistakes to make early on is to assume that you can use any of the general-purpose registers to specify an offset for memory data. Not so! If you try to specify an offset in AX, CX, or DX, the assembler will flag an error.



In real mode, only BP, BX, SI, and DI may hold an offset for memory data.


(This isn't true for more advanced CPUs working in protected mode, as we'll see toward the end of this book.) So, in fact, general-purpose registers AX, CX, and DX aren't quite so general after all. Why was general-purpose register BX singled out for special treatment? Think of it as the difference between dreams and reality for Intel. In the best of all worlds, every register could be used for all purposes. Unfortunately, when CPU designers get together and argue about what their nascent CPU is supposed to do, they are forced to face the fact that there are only so many transistors on the chip to do the job.



Each chip function is given a budget of transistors (sometimes numbering in the tens or even hundreds of thousands). If the desired logic cannot be implemented using that number of transistors, the expectations of the designers have to be brought down a notch and some CPU features shaved from the specification.


The early x86 CPUs including the 8086 and 8088 are full of such compromises. There were not enough transistors available at design time to allow all general-purpose registers to do everything, so in addition to the truly general-purpose ability to hold data, each 8086/8088 register has what I call a "hidden agenda." Each register has some ability that none of the others share. I describe each register's hidden agenda at some appropriate time in this book, and I call it out as such.


In the 20-odd years since the 8086 was created, Intel has hugely expanded the power of its x86 family of CPUs. And sure enough, when you get into 32-bit protected mode, most of the limitations imposed by early transistor budgets go away, and general-purpose registers become almost completely general. However, when acting in real mode (as we're speaking of here), the Pentium, 486, and 386 CPUs take on just about all the characteristics of the 8086 and 8088, including this sort of limitation, which is built into the logic that decodes the instruction set for real mode.


Should you, then, be learning this sort of bad-old-days limitation? I think so. What it teaches you is that limitations exist and need to be remembered. Even the mighty Pentium II has limitations and restrictions. You need to develop a grasp of them, or you'll be floundering around wondering why things don't work.






Using Memory Data



With one or two important exceptions (the string instructions, which I cover to a degree-but not exhaustively-later on), only one of an instruction's two operands may specify a memory location. In other words, you can move an immediate value to memory, or a memory value to a register, or some other similar combination, but you can't move a memory value directly to another memory value. This is just an inherent limitation of the CPU, and we have to live with it, inconvenient as it gets at times.


Specifying a memory address as one of an instruction's operands is a little complicated. The offset address must be resident in one of the general-purpose registers that can legally hold an offset address. (Remember, that's only BP, BX, SI, and DI-not any of the others such as AX, CX, or DX.) To specify that we want the data at the memory location contained in the register rather than the data in the register itself, we use square brackets around the name of the register. In other words, to move the word at address DS:BX into register AX, we would use the following instruction:



  MOV AX,[BX]


Similarly, to move a value residing in register DX into the word at address DS:DI, you would use this instruction:




  MOV [DI],DX






Segment Register Assumptions


The only problem with these examples is this: "DS" isn't anywhere in either instruction. Where does it say to use DS as the segment register?


It doesn't. To keep addressing notation simple, the x86 CPUs in real mode make certain assumptions about certain instructions in combinations with certain registers. There is no comprehensible system to these assumptions, and like dates in history or Spanish irregular verbs, you'll just have to memorize them, or at least know where to look them up. (The where is in Appendix B in this book.)



One of these assumptions is that in working with memory data, the MOV instruction uses the segment address stored in segment register DS unless you explicitly tell it otherwise. In the case of the two preceding examples, we did not tell the MOV instruction to use some segment register other than DS, so it fell back on its assumptions and used DS. However, had you specified the offset as residing in register SP instead of BX or DI, the MOV instruction would have assumed the use of segment register SS instead. This assumption involves a memory mechanism known as the stack, which we won't really address until the next chapter.







Overriding Segment Assumptions for Memory Data


But what if you want to use ES as a segment register for memory data addressed in the MOV instruction? It's not difficult. The instruction set includes what are called segment override prefixes. These are not precisely instructions, but are more like the filters that may be snapped in front of a camera lens. The filter is not itself a lens, but it alters the way the lens operates.


There is one segment override prefix for each of the four segment registers: CS, DS, SS, and ES. In assembly language they are written as the name of the segment register followed by a colon, as shown in Table 7.2.


























Table 7.2: Segment Override Prefixes


SEGMENT OVERRIDE PREFIX




FUNCTION



CS:



Forces use of code segment register CS



DS:




Forces use of the data segment register DS



SS:



Forces use of the stack segment register SS



ES:




Forces use of the extra segment register ES



In use, the segment override prefix is placed immediately in front of the memory data reference whose segment register assumption is to be overridden. For example, to force a MOV instruction to copy a value from the AX register into a location at some offset (contained in SI) into the code segment, you would use this instruction:



  MOV [CS:SI],AX



Without the CS: override prefix, this instruction would move the value of AX into the data segment, at an address specified as DS:SI.


Prefixes in use are very reminiscent of how an address is written; in fact, understanding how prefixes work will help you keep in mind that in every reference to memory data within an instruction, there is a ghostly segment register assumption floating in the air. You may not see the ghostly DS: assumption in your MOV instruction, but if you forget that it's there, the whole concept of memory data will begin to seem arbitrary and magical.


Every reference to memory data includes either an assumed segment register or else a segment override prefix to specify a segment register other than the assumed segment register.


At the machine-code level, a segment override prefix is a single binary byte. The prefix byte is placed in front of rather than within a machine instruction. In other words, if the binary bytes comprising a MOV AX,[BX] instruction are 8BH 07H, adding the ES segment override prefix to the instruction (MOV AX,[ES:BX]) places a single 26H in front of the opcode bytes, giving us 26H 8BH 07H as the full binary equivalent.




If you're sharp, the question will already have occurred to you: What about the flat models? Recall that in both real mode flat model and protected mode flat model, the segment registers all point to the same place and are not changed during the run of the program. In the flat models you do not use segment overrides. What I have explained previously about segment overrides applies only to the real mode segmented model!






Real Mode Memory Data Summary


Real mode memory data consists of a single byte or word in memory, addressed by way of a segment value and an offset value. The register containing the offset address is enclosed in square brackets to indicate that the contents of memory, rather than the contents of the register, are being addressed. The segment register used to address memory data is usually assumed according to a complex set of rules. Optionally, a segment override prefix may be placed in the instruction to specify some segment register other than the default segment register.




Figure 7.1 shows diagrammatically what happens during a MOV AX,[ES:BX] instruction. The segment address component of the full 20-bit memory address is contained inside the CPU in segment register ES. Ordinarily, the segment address would be in register DS, but the MOV instruction contains the ES: segment override prefix. The offset address component is specified to reside in the BX register.



Click To expand

Figure 7.1: How memory data is addressed.


The CPU sends out the values in ES and BX to the memory system side by side. Together, the two values pin down one memory location where MyWord begins. MyWord is actually two bytes, but that's fine-all the x86 CPUs working in real mode (except for the 8088) can bring both bytes into the CPU at once, while the 8088 brings both bytes in separately, one after the other. The CPU handles details like that and you needn't worry about it. Because AX is a 16-bit register, of course, two 8-bit bytes can fit into it quite nicely.


The segment address may reside in any of the four segment registers: CS, DS, SS, or ES. However, the offset address may reside only in registers BX, BP, SP, SI, or DI. AX, CX, and DX may not be used to contain an offset address during real mode memory addressing.







Limitations of the MOV Instruction


The MOV instruction can move nearly any register to any other register. For reasons having to do with the limited budget of transistors on the 8086 and 8088 chips, MOV can't quite do any move you can think of-in real mode, at least. Here's a list of MOV's real mode limitations:







  1. MOV cannot move memory data to memory data. In other words, an instruction like MOV [SI],[BX] is illegal. Either of MOV's two operands may be memory data, but both cannot be at once.






  2. MOV cannot move one segment register into another. Instructions like MOV CS,SS are illegal. This could have been handy, but it simply can't be done.





  3. MOV cannot move immediate data into a segment register. You can't code up MOV CS,0B800H. Again, it would be handy but you just can't do it.







  4. MOV cannot move one of the 8-bit register halves into a 16-bit register, nor vice versa. There are easy ways around any possible difficulties here, and preventing moves between operands of different sizes can keep you out of numerous kinds of trouble.




These limitations, of course, are over and above those situations that simply don't make sense: moving a register or memory into immediate data, moving immediate data into immediate data, specifying a general-purpose register as a segment register to contain a segment, or specifying a segment register to contain an offset address. Table 7.3 shows numerous illegal MOV instructions that illustrate these various limitations and nonsense situations.









































Table 7.3: Rogue MOV Instructions


ILLEGAL MOV INSTRUCTION




WHY IT'S ILLEGAL



MOV 17,1



Only one operand may be immediate data.



MOV 17,BX




Only the source operand may be immediate data.



MOV CX,DH



The operands must be the same size.



MOV [DI],[SI]




Only one operand may be memory data.



MOV DI,[DX:BX]



DX is not a segment register.



MOV ES,0B800




Segment registers may not be loaded from immediate data.



MOV DS,CS



Only one operand may be a segment register.



MOV [AX],BP




AX may not address memory data (nor may CX or DX).



MOV SI,[CS]



Segment registers may not address memory data.







Some Notes on Assembler Syntax


Although we haven't talked about it a whole lot just yet, this book focuses on a particular assembler called NASM. And if this book is your first exposure to assembly language, nothing I've said so far should cause you any cognitive dissonance with your earlier experience, since you have no earlier experience. However, if you've played with assembly language using other assemblers, you will soon begin to see small differences between what you once learned in writing assembly language mnemonics and what I'm teaching in this book. These differences are matters of syntax, and they may become important, especially if you ever try to convert source code to NASM from another assembler such as MASM, TASM, or A86.



In the best of all worlds, every assembler would respond in precisely the same way to all the same mnemonics and directives set up all the same ways. In reality, syntax differs. Here's a common example: In Microsoft's MASM, memory data that includes a segment override must be coded like this:



  MOV AX,ES:[BX]



Note here that the segment override "ES:" is outside the brackets enclosing BX. NASM places the overrides inside the brackets:



  MOV AX,[ES:BX]


These two lines perform precisely the same job. The people who wrote NASM feel (and I concur) that it makes far more sense to place the override inside the brackets than outside. The difference is purely one of syntax. The two instructions mean precisely the same thing, right down to generating the very same binary machine code: 3E 8B 07.


Worse, when you enter the same thing in DEBUG, it must be done this way:




  ES: MOV AX,[BX]


Differences in syntax will drive you crazy on occasion, especially when flipping between NASM and DEBUG. It's best to get a firm grip on what the instructions are doing, and understand what's required to make a particular instruction assemble correctly. I point out some common differences between NASM and MASM throughout this book, since MASM is by far the most popular assembler in the x86 world, and more people have been exposed to it than any other.




















Previous Section

< Day Day Up >

Next Section



Reading and Using an Assembly Language Reference


The MOV instruction is a good start. Like a medium-sized screwdriver, you'll end up using it for normal tasks and maybe some abnormal ones, just as I use screwdrivers to pry nails out of boards, club black widow spiders in the garage bathroom, discharge large electrolytic capacitors, and other intriguing things over and above workaday screw turning. (Not all of these are a good idea ... but then again, many have said that assembly language programming isn't a good idea ...) The x86 instruction set contains dozens of instructions, however, and over the course of the rest of this book, I mix in descriptions of various other instructions with further discussions of memory addressing and program logic and design.


Remembering a host of tiny, tangled details involving dozens of different instructions is brutal and unnecessary. Even the Big Guys don't try to keep it all between their ears at all times. Most keep a blue card or some other sort of reference document handy to jog their memories about machine instruction details.




Blue Cards



A blue card is a reference summary printed on a piece of colored card stock. It folds up like a road map and fits in your pocket. The original blue card may actually have been blue, but knowing the perversity of programmers in general, it was probably bright orange.


Blue cards aren't always cards anymore. One of the best is a full sheet of very stiff shiny plastic, sold by Micro Logic Corporation of Hackensack, New Jersey. The one sold with Microsoft's MASM is actually published by Intel and has grown to a pocket-sized booklet stapled on the spine.


Blue cards contain very terse summaries of what an instruction does, which operands are legal, which flags it affects, and how many machine cycles it takes to execute. This information, while helpful in the extreme, is often so tersely put that newcomers might not quite fathom which edge of the card is up.






An Assembly Language Reference for Beginners


In deference to people just starting out in assembly language, I have put together a beginner's reference to the most common x86 instructions and called it Appendix A. It contains at least a page on every instruction I cover in this book, plus a few additional instructions that everyone ought to know. It does not include descriptions on every instruction, but only the most common and most useful. Once you've gotten skillful enough to use the more arcane instructions, you should be able to read the NASM documentation (or that of some other assembler) and run with it.


On page 213 is a sample entry from Appendix A. Refer to it during the following discussion.


The instruction's mnemonic is at the top of the page, highlighted in a box to make it easy to spot while flipping quickly through the appendix. To the mnemonic's right is the name of the instruction, which is a little more descriptive than the naked mnemonic.






Flags


Immediately beneath the mnemonic is a minichart of machine flags in the Flags register. I haven't spoken in detail of flags yet, but the Flags register is a collection of 1-bit values that retain certain essential information about the state of the machine for short periods of time. Many (but by no means all) x86 instructions change the values of one or more flags. The flags may then be individually tested by one of the JMP instructions, which then change the course of the program depending on the state of the flags.



We'll get into this business of tests and jumps in Chapter 10. For now, simply understand that each of the flags has a name, and that for each flag is a symbol in the flags minichart. You'll come to know the flags by their two-character symbols in time, but until then, the full names of the flags are shown to the right of the minichart. The majority of the flags are not used frequently in beginning assembly language work. Most of what you'll be paying attention to, flags-wise, is the Carry flag (CF). It's used, as you might imagine, for keeping track of binary arithmetic when an arithmetic operation carries out of a single byte or word.


There will be an asterisk (*) beneath the symbol of any flag affected by the instruction. How the flag is affected depends on what the instruction does. You'll have to divine that from the Notes section. When an instruction affects no flags at all, the word <none> will appear in the minichart.


In the example page, the minichart indicates that the NEG instruction affects the Overflow flag, the Sign flag, the Zero flag, the Auxiliary carry flag, the Parity flag, and the Carry flag. The ways that the flags are affected depend on the results of the negation operation on the operand specified. These ways are summarized in the second paragraph of the Notes section.




NEG Negate (Two's Complement; That Is, Multiply by −1)




Flags affected:


       O D I T S Z A P C  OF: Overflow flag  TF: Trap flag AF: Aux carry
F F F F F F F F F DF: Direction flag SF: Sign flag PF: Parity flag
* * * * * * IF: Interrupt flag ZF: Zero flag CF: Carry flag







Legal forms: 8086/8 286 386 486 Pentium


       NEG r8          X             X         X        X            X
NEG m8 X X X X X
NEG r16 X X X X X
NEG m16 X X X X X
NEG r32 X X X
NEG m32 X X X






Examples:


       NEG AL
NEG ECX
NEG BYTE [BX] ; Negates byte quantity at DS:BX
NEG WORD [DI] ; Negates word quantity at DS:BX







Notes:

This is the assembly language equivalent of multiplying a value by −1. Keep in mind that negation is not the same as simply inverting each bit in the operand. (Another instruction, NOT, does that.) The process is also known as generating the two's complement of a value. The two's complement of a value added to that value yields zero. −1 = $FF; −2 = $FE; −3 = $FD; and so forth.


If the operand is 0, CF is cleared and ZF is set; otherwise, CF is set and ZF is cleared. If the operand contains the maximum negative value (−128 for 8-bit or −32768 for 16-bit), the operand does not change, but OF and CF are set. SF is set if the result is negative, else SF is cleared. PF is set if the low-order 8 bits of the result contain an even number of set (1) bits; otherwise, PF is cleared.








Note

You must use a type override specifier (BYTE or WORD) with memory data.




       r8 = AL AH BL BH CL CH DL DH       r16 = AX BX CX DX BP SP SI DI
sr = CS DS SS ES
m8 = 8-bit memory data m16 = 16-bit memory data
i8 = 8-bit immediate data i16 = 16-bit immediate data
d8 = 8 bit signed displacement d16 = 16-bit signed displacement












Legal Forms


A given mnemonic represents a single x86 instruction, but each instruction may include more than one legal form. The form of an instruction varies by the type and order of the operands passed to it.


What the individual forms actually represent are different binary number opcodes. For example, beneath the surface, the POP AX instruction is the number 58H, whereas the POP SI instruction is the number 5EH.



Sometimes there will be special cases of an instruction and its operands that are shorter than the more general cases. For example, the XCHG instruction, which exchanges the contents of the two operands, has a special case when one of the operands is register AX. Any XCHG instruction with AX as one of the operands is represented by a single-byte opcode. The general forms of XCHG (for example, XCHG r16,r16) are always 2 bytes long instead. This implies that there are actually two different opcodes that will do the job for a given combination of operands; for example, XCHG AX,DX. True enough—and some assembler programs are smart enough to choose the shortest form possible in any given situation. If you are hand-assembling a sequence of raw opcode bytes, say, for use in a higher-level language INLINE statement, you need to be aware of the special cases, and all special cases will be marked as such in the Legal forms section.



When you want to use an instruction with a certain set of operands, make sure you check the Legal forms section of the reference guide for that instruction to make sure that the combination is legal. The MOV instruction, for example, cannot move one segment register directly into another, nor can it move immediate data directly into a segment register. Neither combination of operands is a legal form of the MOV instruction, though they make sense and would be nice to have.


In the example reference page on the NEG instruction, you see that a segment register cannot be an operand to NEG. (If it could, there would be a NEG sr item in the Legal forms list.) If you want to negate the value in a segment register, you'll first have to use MOV to move the value from the segment register into one of the general-purpose registers before using NEG on the general-purpose register, and finally moving the negated value back into the segment register. (Note well that using NEG on a segment register is an almighty peculiar thing to do, and for that reason, that form of NEG was not given any transistor budget in the real mode portion of the x86 CPUs.)






Operand Symbols


The symbols used to indicate the nature of the operands in the Legal forms section are summarized at the bottom of every page in the reference appendix. They're close to self-explanatory, but I'll take a moment to expand upon them slightly here:





  • r8— An 8-bit register half, one of AH, AL, BH, BL, CH, CL, DH, or DL.






  • r16— A 16-bit general-purpose register, one of AX, BX, CX, DX, BP, SP, SI, or DI.





  • sr— One of the four segment registers, CS, DS, SS, or ES.





  • m8— An 8-bit byte of memory data.






  • m16— A 16-bit word of memory data.





  • m32— A 32-bit word of memory data.






  • i8— An 8-bit byte of immediate data.





  • i16— A 16-bit word of immediate data.





  • i32— A 32-bit word of immediate data.






  • d8— An 8-bit signed displacement. We haven't covered these yet, but a displacement is a distance between the current location in the code and another place in the code to which we want to jump. It's signed (that is, either negative or positive) because a positive displacement jumps you higher (forward) in memory, whereas a negative displacement jumps you lower (back) in memory. We examine this notion in detail in Chapter 10.






  • d16— A 16-bit signed displacement. Again, for use with jump and call instructions. See Chapter 10.





  • d32— A 32-bit signed displacement.








Examples


Whereas the Legal forms section shows what combinations of operands is legal for a given instruction, the Examples section shows examples of the instruction in actual use, just as it would be coded in an assembly language program. I've tried to put a good sampling of examples for each instruction, demonstrating the range of different possibilities with the instruction. This includes situations that require type override specifiers, which I cover in the next section.






Notes


The Notes section of the reference page describes the instruction's action briefly and provides information on how it affects the flags, how it may be limited in use, and any other detail that needs to be remembered, especially things that beginners would overlook or misconstrue.






What's Not Here ...



Appendix A differs from most detailed assembly language references in that it does not have the binary opcode encoding information, nor indications of how many machine cycles are used by each form of the instruction.


The binary encoding of an instruction is the actual sequence of binary bytes that the CPU digests and recognizes as the machine instruction. What we would call POP AX, the machine sees as the binary number 58H. What we call ADD SI,07733H, the machine sees as the 4-byte sequence 81H 0C6H 33H 77H. Machine instructions are encoded into anywhere from one to four (rarely more) binary bytes depending on what instruction they are and what their operands are. Laying out the system for determining what the encoding will be for any given instruction is extremely complicated, in that its component bytes must be set up bit by bit from several large tables. I've decided that this book is not the place for that particular discussion and have left encoding information out of the reference appendix.


Finally, I've included nothing anywhere in this book that indicates how many machine cycles are expended by any given machine instruction. A machine cycle is one pulse of the master clock that makes the PC perform its magic. Each instruction uses some number of those cycles to do its work, and the number varies all over the map depending on criteria that I won't be explaining in this book.



Furthermore, as Michael Abrash explains in his immense book Michael Abrash's Graphics Programming Black Book (Coriolis Group Books, 1997), knowing the cycle requirements for individual instructions is rarely sufficient to allow even an expert assembly language programmer to calculate how much time a given series of instructions will take. He and I both agree that it is no fit subject for beginners, and I will let him take it up in his far more advanced volume.




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 (up-memory 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.




Using Type Specifiers


Back on the sample reference appendix page (see page 212), notice the following example uses of the NEG instruction:



  NEG BYTE [BX] ; Negates byte quantity at DS:BX
NEG WORD [DI] ; Negates word quantity at DS:BX


Why BYTE [BX]? Or WORD [DI]? Used in this way, BYTE and WORD are what we call type specifiers, and you literally can't use NEG (or numerous other machine instructions) on memory data without one or the other. They are not instructions in the same sense that NEG is an instruction. They exist in the broad class of things we call directives. Directives give instructions to the assembler. In this case, they tell the assembler how large the operand is when there is no other way for the assembler to know.



The problem is this: The NEG instruction negates its operand. The operand can be either a byte or a word; in real mode, NEG works equally well on both. But ... how does NEG know whether to negate a byte or a word? The memory data operand [BX] only specifies an address in memory, using DS as the assumed segment register. The address DS:BX points to a byte-but it also points to a word, which is nothing more than two bytes in a row somewhere in memory. So, does NEG negate the byte located at address DS:BX? Or does it negate the two bytes (a word) that start at address DS:BX?



Unless you tell it somehow, NEG has no way to know.


Telling an instruction the size of its operand is what BYTE and WORD do. Several other instructions that work on single operands only (such as INC, DEC, and NOT) have the same problem and use type specifiers to resolve this ambiguity.





Types in Assembly Language


Unlike nearly all high-level languages such as Pascal and C++, the notion of type in assembly language is almost wholly a question of size. A word is a type, as is a byte, a double word, a quad word, and so on. The assembler is unconcerned with what an assembly language variable means. (Keeping track of such things is totally up to you.) The assembler only worries about how big it is. The assembler does not want to have to try to fit 10 pounds of kitty litter in a 5-pound bag, which is impossible, nor 5 pounds of kitty litter in a 10-pound bag, which can be confusing and under some circumstances possibly dangerous.


Register data always has a fixed and obvious type, since a register's size cannot be changed. BL is one byte and BX is two bytes.


The type of immediate data depends on the magnitude of the immediate value. If the immediate value is too large to fit in a single byte, that immediate value becomes word data and you can't load it into an 8-bit register half. An immediate value that can fit in a single byte may be loaded into either a byte-sized register half or a full word-sized register; its type is thus taken from the context of the instruction in which it exists and matches that of the register data operand into which it is to be loaded. But if you try to load immediate data into a destination that's too small for it, the assembler will give you an error. Here's a trivial example:




  MOV BL,0FFFFH


When it encounters this, NASM will complain by saying, "Warning: Byte value exceeds bounds." BL can hold values from 0 to 0FFH. (0 to 255). The value 0FFFFH is out of bounds because it is much larger than 0FFH.


Memory data is something else again. We've spoken of memory data so far in terms of registers holding offsets without considering the use of named memory data. I discuss named memory data in the next chapter, but in brief terms, you can define named variables in your assembly language programs using such directives as DB and DW. It looks like this:



  Counter     DB 0
MixTag DW 32



Here, Counter is a variable allocated as a single byte in memory by the DB (Define Byte) directive. Similarly, MixTag is a variable allocated as a word in memory by the DW (Define Word) directive.



By using DB, you give variable Counter a type and hence a size. You must match this type when you use the variable name Counter in an instruction to indicate memory data. The way to do this is to use the BYTE directive, as I mentioned a little earlier. This, for example, will be accepted by the assembler:



  MOV BL,BYTE [Counter]


This instruction will take the current value located in memory at the address represented by the variable name Counter and will load that variable into register half BL. You might wonder: Why do I need to put the BYTE directive there? The assembler should know that Counter is 1 byte in size because it was defined using the directive DB.



In some assemblers, including Microsoft's MASM, it would. However, NASM's authors feel that it's important to be as explicit with assemblers as possible and leave little or nothing for the assembler to infer from context. So, although NASM uses the DB directive to allocate one byte of memory for the variable Counter, it does not remember that Counter takes up only one byte when you insert Counter as an operand in a machine instruction. You must build that specification into your source code, by using the BYTE directive. This will force you to think a little bit more about what you're doing at every point that you do it; that is, right where you use variable names as instruction operands. Doing so may help you avoid certain really stupid mistakes-like the ones I used to make all the time while I was working with MASM, most of which came out of trying to let the assembler do my thinking for me.



To me, this is a wonderful thing, and one of the main reasons I chose NASM as the focus of this book.


Now here's another case, one that NASM will assemble without a burp:



  MOV BL,BYTE MixTag


This looks innocent enough until you remember that MixTag is actually 2 bytes (one word) in size, having been defined with the DW directive. You might think this is an error, because MixTag isn't the same size as BL. True enough-but the key is that there's no ambiguity here. The assembler knows what you want, even if what you want is peculiar. The type specifier BYTE forces the assembler to look upon MixTag as being 1 byte in size. MixTag is not byte-sized, however, so what actually happens is that the least significant (lowermost) byte of MixTag will be loaded into BL, with the most significant byte left high and dry.



Is this useful? It can be. Is it dangerous? You bet. It is up to you to decide whether overriding the type of memory data makes sense and is completely your responsibility to ensure that doing so doesn't sprinkle your code with bugs. But nothing is left for the assembler to decide. That's what type specifiers are for: to make it clear to the assembler in every case what it is supposed to do. Whether that in fact makes sense is up to you. Use your head-and know what you're doing. That's more important in assembly language than anywhere else in computer programming.

8


The Bones of an Assembly Language Program



The following listing is perhaps the simplest correct program that will do anything visible and still be comprehensible and expandable. This issue of comprehensibility is utterly central to quality assembly language programming. With no other computer language (not even APL or that old devil FORTH) is there anything even close to the risk of writing code that looks so much like something scraped off the wall of King Tut's tomb.


The program EAT.ASM displays one (short) line of text on your display screen:



  Eat at Joe's!


For that you have to feed 28 lines of text file to the assembler. Many of those 28 lines are unnecessary in the strict sense, but serve instead as commentary to allow you to understand what the program is doing (or more important, how it's doing it) six months or a year from now.



One of the aims of assembly language coding is to use as few instructions as possible in getting the job done. This does not mean creating as short a source code file as possible. (The size of the source file has nothing to do with the size of the executable file assembled from it!) The more comments you put in your file, the better you'll remember how things work inside the program the next time you pick it up. I think you'll find it amazing how quickly the logic of a complicated assembly language file goes cold in your head. After no more than 48 hours of working on other projects, I've come back to assembler projects and had to struggle to get back to flank speed on development.


Comments are neither time nor space wasted. IBM used to say, One line of comments per line of code. That's good-and should be considered a minimum for assembly language work. A better course (that I will in fact follow in the more complicated examples later on) is to use one short line of commentary to the right of each line of code, along with a comment block at the start of each sequence of instructions that work together in accomplishing some discrete task.


Here's the program. Read it carefully:



  ; Source name     : EAT.ASM
; Executable name : EAT.COM
; Code model: : Real mode flat model

; Version : 1.0
; Created date : 6/4/1999
; Last update : 9/10/1999
; Author : Jeff Duntemann
; Description : A simple example of a DOS .COM file programmed using
; NASM-IDE 1.1 and NASM 0.98.


[BITS 16] ; Set 16 bit code generation
[ORG 0100H] ; Set code start address to 100h (COM file)

[SECTION .text] ; Section containing code
START:

mov dx, eatmsg ; Mem data ref without [] loads the ADDRESS!
mov ah,9 ; Function 9 displays text to standard output.
int 21H ; INT 21H makes the call into DOS.

mov ax, 04C00H ; This DOS function exits the program
int 21H ; and returns control to DOS.

[SECTION .data] ; Section containing initialized data

eatmsg db "Eat at Joe's!", 13, 10, "$" ;Here's our message




The Simplicity of Flat Model


After all our discussion in previous chapters about segments, this program might seem, um,…suspiciously simple. And indeed it's simple, and it's simple almost entirely because it's written for the 16-bit real mode flat model. (I drew this model out in Figure 6.8.) The first thing you'll notice is that there are no references to segments or segment registers anywhere. The reason for this is that in real mode flat model, you are inside a single segment, and everything you do, you do within that single segment. If everything happens within one single segment, the segments (in a sense) "factor out" and you can imagine that they don't exist. Once we assemble EAT.ASM and create a runnable program from it, I'll show you what those segment registers are up to and how it is that you can almost ignore them in real mode flat model.


But first, let's talk about what all those lines are doing.



At the top is a summary comment block. This text is for your use only. When NASM processes a .ASM file, it strips out and discards all text between any semicolon and the end of the line the semicolon is in. Such lines are comments, and they serve only to explain what's going on in your program. They add nothing to the executable file, and they don't pass information to the assembler. I recommend placing a summary comment block like this at the top of every source code file you create. Fill it with information that will help someone else understand the file you've written or that will help you understand the file later on, after it's gone cold in your mind.


Beneath the comment block is a short sequence of commands directed to the assembler. These commands are placed in square brackets so that NASM knows that they are for its use, and are not to be interpreted as part of the program.


The first of these commands is this:



  [BITS 16]          ; Set 16 bit code generation


The BITS command tells NASM that the program it's assembling is intended to be run in real mode, which is a 16-bit mode. Using [BITS 32] instead would have brought into play all the marvelous 32-bit protected mode goodies introduced with the 386 and later x86 CPUs. On the other hand, DOS can't run protected mode programs, so that wouldn't be especially useful.


The next command requires a little more explanation:




  [ORG 0100h]        ; Set code start address to 100h (COM file)


"ORG" is an abbreviation of origin, and what it specifies is sometimes called the origin of the program, which is where code execution begins. Code execution begins at 0100H for this program. The 0100h value (the h and H are interchangeable) is loaded into the instruction pointer IP by DOS when the program is loaded and run. So, when DOS turns control over to your program (scary thought, that!), the first instruction to be executed is the one pointed to by IP-in this case, at 0100H.



Why 0100H? Look back at Figure 6.8. The real mode flat model (which is often called the .COM file model) has a 256-byte prefix at the beginning of its single segment. This is the Program Segment Prefix (PSP) and it has several uses that I won't be explaining here. The PSP is basically a data buffer and contains no code. The code cannot begin until after the PSP, so the 0100H value is there to tell DOS to skip those first 256 bytes.


The next command is this:



  [SECTION .text]    ; Section containing code



NASM divides your programs into what it calls sections. These sections are less important in real mode flat model than in real mode segmented model, when sections map onto segments. (More on this later.) In flat model, you have only one segment. But the SECTION commands tell NASM where to look for particular types of things. In the .text section, NASM expects to find program code. A little further down the file you'll see another SECTION command, this one for the .data section. In the .data section, NASM expects to find the definitions for your initialized variables. A third section is possible, the .bss section, which contains uninitialized data. EAT.ASM does not use any uninitialized data, so this section does not exist in this program. I discuss uninitialized data later on, in connection with the stack.






Labels


The next item in the file is something called a label:



  START:


A label is a sort of bookmark, holding a place in the program code and giving it a name that's easier to remember than a memory address. The START: label indicates where the program begins. Technically speaking, the START: label isn't necessary in EAT.ASM. You could eliminate the START: label and the program would still assemble and run. However, I think that every program should have a START: label as a matter of discipline. That's why EAT.ASM has one.



Labels are used to indicate where JMP instructions should jump to, and I explain that in detail later in this chapter and in later chapters. The only distinguishing characteristic of labels is that they're followed by colons. Some rules govern what constitutes a valid label:





  • Labels must begin with a letter or with an underscore, period, or question mark. These last three have special meanings (especially the period), so I recommend sticking with letters until you're way further along in your study of assembly language and NASM.





  • Labels must be followed by a colon when they are defined. This is basically what tells NASM that the identifier being defined is a label. NASM will punt if no colon is there and will not flag an error, but the colon nails it, and prevents a misspelled mnemonic from being mistaken for a label. So use the colon!







  • Labels are case sensitive. So yikes:, Yikes:, and YIKES: are three completely different labels. This differs from practice in a lot of languages (Pascal particularly) so keep it in mind.




Later on, we'll see such labels used as the targets of jump instructions. For example, the following machine instruction transfers the flow of instruction execution to the location marked by the label GoHome:



  JMP GoHome



Notice that the colon is not used here. The colon is only placed where the label is defined, not where it is referenced. Think of it this way: Use the colon when you are marking a location, not when you are going there.






Variables for Initialized Data


The identifier eatmsg defines a variable. Specifically, eatmsg is a string variable (more on which follows) but still, as with all variables, it's one of a class of items we call initialized data: something that comes with a value, and not just a box that will accept a value at some future time. A variable is defined by associating an identifier with a data definition directive. Data definition directives look like this:




  MyByte      DB 07H            ; 8 bits in size
MyWord DW 0FFFFH ; 16 bits in size
MyDouble DD 0B8000000H ; 32 bits in size


Think of the DB directive as "Define Byte." DB sets aside one byte of memory for data storage. Think of the DW directive as "Define Word." DW sets aside one word of memory for data storage. Think of the DD directive as "Define Double." DD sets aside a double word in memory for storage, typically for full 32-bit addresses.



I find it useful to put some recognizable value in a variable whenever I can, even if the value is to be replaced during the program's run. It helps to be able to spot a variable in a DEBUG dump of memory rather than to have to find it by dead reckoning-that is, by spotting the closest known location to the variable in question and counting bytes to determine where it is.






String Variables


String variables are an interesting special case. A string is just that: a sequence or string of characters, all in a row in memory. A string is defined in EAT.ASM:



  eatmsg   DB "Eat at Joe's!", 13, 10, "$" ;Here's our message



Strings are a slight exception to the rule that a data definition directive sets aside a particular quantity of memory. The DB directive ordinarily sets aside one byte only. However, a string may be any length you like, as long as it remains on a single line of your source code file. Because there is no data directive that sets aside 16 bytes, or 42, strings are defined simply by associating a label with the place where the string starts. The eatmsg label and its DB directive specify one byte in memory as the string's starting point. The number of characters in the string is what tells the assembler how many bytes of storage to set aside for that string.


Either single quote (') or double quote (") characters may be used to delineate a string, and the choice is up to you, unless you're defining a string value that itself contains one or more quote characters. Notice in EAT.ASM the string variable eatmsg contains a single-quote character used as an apostrophe. Because the string contains a single-quote character, you must delineate it with double quotes. The reverse is also true: If you define a string that contains one or more double-quote characters, you must delineate it with single-quote characters:




  Yukkh    DB    'He said, "How disgusting!" and threw up.',"$"


You may combine several separate substrings into a single string variable by separating the substrings with commas. Both eatmsg and Yukkh do this. Both add a dollar sign ($) in quotes to the end of the main string data. The dollar sign is used to mark the end of the string for the mechanism that displays the string to the screen. More on that mechanism and marking string lengths in a later section.


What, then, of the "13,10" in eatmsg? This is the carriage return and linefeed pair I discussed in an earlier chapter. Inherited from the ancient world of electromechanical Teletype machines, these two characters are recognized by DOS as meaning the end of a line of text that is output to the screen. If anything further is output to the screen, it will begin at the left margin of the next line below. You can concatenate such individual numbers within a string, but you must remember that they will not appear as numbers. A string is a string of characters. A number appended to a string will be interpreted by most operating system routines as an ASCII character. The correspondence between numbers and ASCII characters is shown in Appendix D.






Directives versus Instruction Mnemonics


Data definition directives look a little like machine instruction mnemonics, but they are emphatically not machine instructions. One very common mistake made by beginners is looking for the binary opcode represented by a directive such as DB or DW. There is no binary opcode for DW, DB, and the other directives. Machine instructions, as the name implies, are instructions to the CPU itself. Directives, by contrast, are instructions to the assembler.



Understanding directives is easier when you understand the nature of the assembler's job. (Look back to Chapter 4 for a detailed refresher if you've gotten fuzzy on what assemblers and linkers do.) The assembler scans your source code text file, and as it scans your source code file it builds an object code file on disk. It builds this object code file step by step, one byte at a time, starting at the beginning of the file and working its way through to the end. When it encounters a machine instruction mnemonic, it figures out what binary opcode is represented by that mnemonic and writes that binary opcode (which may be anywhere from one to six actual bytes) to the object code file.


When the assembler encounters a directive such as DW, it does not write any opcode to the object code file. DW is a kind of signpost to the assembler, reading "Set aside two bytes of memory right here, for the value that follows." The DW directive specifies an initial value for the variable, and so the assembler writes the bytes corresponding to that value in the two bytes it set aside. The assembler writes the address of the allocated space into a table, beside the label that names the variable. Then the assembler moves on, to the next directive (if there are further directives) or to whatever comes next in the source code file.


For example, when you write the following statement in your assembly language program:




  MyVidOrg    DW    0B800H


what you are really doing is instructing the assembler to set aside two bytes of data (Define Word, remember) and place the value 0B800H in those two bytes. The assembler writes the identifier MyVidOrg and the variable's address into a table it builds of identifiers (both labels and variables) in the program for later use by other elements of the program, or the linker.







The Difference between a Variable's Address and Its Contents


I've left discussion of EAT.ASM's machine instructions for last-at least in part because they're easy to explain. All that EAT.ASM does, really, is hand a string to DOS and tell DOS to display it on the screen by sending it to something called standard output. It does this by passing the address of the string to DOS-not the character values contained in the string itself. This is a crucial distinction that trips up a lot of beginners. Here's the first instruction in EAT.ASM:



  mov  dx, eatmsg    ; Mem data ref without [] loads the ADDRESS!


If you look at the program, you can see that while DX is 2 bytes in size, the string eatmsg is 15 bytes in size. At first glance, this MOV instruction would seem impossible-but that's because what's being moved is not the string itself, but the string's address, which (in the real mode flat model) is 16 bits-2 bytes-in size. The address will thus fit nicely in DX.



When you place a variable's identifier in a MOV instruction, you are accessing the variable's address, as explained previously. By contrast, if you want to work with the value stored in that variable, you must place the variable's identifier in square brackets. Suppose you had defined a variable in the .data section called MyData this way:



  MyData    DW    0744H


The identifier MyData represents some address in memory, and at that address the assembler places the value 0744H. Now, if you want to copy the value contained in MyData to the AX register, you would use the following MOV instruction:




  MOV AX,[MyData]


After this instruction, AX would contain 0744H.


There are many situations in which you need to move the address of a variable into a register rather than the contents of the variable. In fact, you may find yourself moving the addresses of variables around more than the contents of the variables, especially if you make a lot of calls to DOS and BIOS services.


If you've used higher-level languages such as Basic and Pascal, this distinction may seem inane. After all, who would mistake the contents of a variable for its location? Well, that's easy for you to say-in Basic and Pascal you rarely if ever even think about where a variable is. The language handles all that rigmarole for you. In assembly language, knowing where a variable is located is essential in order to do lots of important things.






Making DOS Calls


What EAT.ASM really does, as I mentioned previously, is call DOS and instruct DOS to display a string located at a particular place in memory. The string itself doesn't go anywhere; EAT.ASM tells DOS where the string is located, and then DOS reaches up into your .data section and does what it must with the string data.


Calling DOS is done with something called a software interrupt. I explain these in detail later in this chapter. But if you look at the code you can get a sense for what's going on:



  mov  dx, eatmsg    ; Mem data ref without [] loads the ADDRESS!
mov ah,9 ; Function 9 displays text to standard output.
int 21H ; INT 21H makes the call into DOS.



Here, the first line loads the address of the string into register DX. The second line simply loads the constant value 9 into register AH. The third line makes the interrupt call, to interrupt 21H.


The DOS call has certain requirements that must be set up before the call is made. It must know what particular call you want to make, and each call has a number. This number must be placed in AH and, in this case, is call 09H (Display String). For this particular DOS call, DOS expects the address of the string to be displayed to be in register DX. If you satisfy those two conditions, you can make the DOS software interrupt call INT 21H-and there's your string on the screen!






Exiting the Program and Setting ERRORLEVEL


Finally, the job is done, Joe's has been properly advertised, and it's time to let DOS have the machine back. Another DOS service, 4CH (Terminate Process), handles the mechanics of courteously disentangling the machine from EAT.ASM's clutches. Terminate Process doesn't need the address of anything, but it will take whatever value it finds in the AL register and place it in the ERRORLEVEL DOS variable. DOS batch programs can test the value of ERRORLEVEL and branch on it.


EAT.ASM doesn't do anything worth testing in a batch program, but if ERRORLEVEL will be set anyway, it's a good idea to provide some reliable and harmless value for ERRORLEVEL to take. This is why 0 is loaded into AL prior to ending it all by the final INT 21 instruction. If you were to test ERRORLEVEL after running EAT.EXE, you would find it set to 0 in every case.



That's really all there is to EAT.ASM. Now let's see what it takes to run it, and then let's look more closely at its innards in memory.




Assembling and Running EAT.ASM


To assemble and run EAT.ASM, we can load it into NASM-IDE, and then let NASM-IDE invoke NASM. That's how we're going to do it here. You should understand, however, that NASM-IDE is simply a "place to stand." NASM is what actually does the work of assembling the file.


Here's the sequence:




  1. Run NASM-IDE.




  2. Select the Open item from the File menu. (We would say this, in shorthand form, "Select File|Open.")





  3. Highlight the name of file EAT.ASM, and click on the OK button. EAT.ASM will load and be displayed in a window. If EAT.ASM isn't in the same directory as NASM-IDE, you may have to navigate to the directory where EAT.ASM lives by clicking on directory names in the dialog box.




  4. Select Assemble|Assemble. The Error window will appear in the lower half of the display, even if only to tell you, "No errors occurred."




  5. Assuming no errors occurred, select Assemble|Run. The display will clear, and EAT's message will be displayed in the upper-left corner of the display. Beneath it you'll see DOS's message, "Press any key to continue…" Press any key, and the display will return to NASM-IDE, showing EAT.ASM.






Assembler and Interactive Development Environment



There it is: You've assembled and run an assembly language program. It's important at this point to ponder who's doing what on your system. If you read Chapter 5, you know that NASM-IDE is an interactive development environment (IDE) containing a source code editor and a few other tools. NASM-IDE is not the assembler. The assembler is called NASM, and NASM is a separate program that does not actually require NASM-IDE for its use. When you select Assemble | Assemble in NASM-IDE, the NASM-IDE program invokes the NASM assembler behind the scenes and passes the name of the program you're working on to NASM, which assembles it and writes the file EAT.COM to disk. Later, when you select Assemble | Run in NASM-IDE, the NASM-IDE program runs EAT.COM for you.


Technically, you don't need NASM-IDE. You can invoke the assembler yourself from the DOS command line, and you can of course run the generated EAT.COM file by naming it on the command line as well. NASM-IDE is there to save you time and let you make changes and reassemble your program quickly and with less keyboarding.


You should, however, understand what NASM-IDE is doing. One major thing it's doing for you is constructing a proper command line by which to invoke NASM. To treat EAT.ASM as a program written for real mode flat model and to generate EAT.COM from EAT.ASM, the following command line has to be used to invoke NASM:



  C:\>NASM16 EAT.ASM -F BIN -O EAT.COM



It's certainly easier just selecting Assemble | Assemble, no? Still, over time you should study the various command-line options that NASM supports so that you can begin to do more advanced things than NASM-IDE is capable of doing. They're all described in detail in NASM's documentation, which is present on the CD ROM for this book.


This particular command line is fairly easy to explain:




  1. NASM16 is the name of the version of NASM intended for use with real mode programs under DOS. On your disk it will be NASM16.EXE.




  2. EAT.ASM is the name of the source code file you wish to assemble.




  3. -F BIN indicates the output format. There are many different types of files that NASM is capable of producing. The one we want is the .COM file, which is generated as a simple binary image of the generated machine-code bytes and any initialized data. The "BIN" indicates "binary image." The other key thing about .COM files is the 0100H code origin, but that's handled in the source code, as I explained earlier.






  4. -O EAT.COM is the name of the output file. You can call the generated code file anything you want. If you don't specify the name of the output file, NASM will just lop off the ".ASM" and call the file EAT. Unfortunately, the name "EAT" doesn't indicate to DOS that it's a runnable program, so DOS won't know what to do with it. That's why you have to specify the full output file name "EAT.COM" on the command line.




Later on in this book, we're going to invoke NASM from the command line to produce a type of file that NASM-IDE won't be able to tell NASM how to produce. Therefore, we'll have to do it ourselves.






What Happens When a .COM File Runs


It's often useful to know just what happens when you run a program of your own creation. DOS treats its two kinds of executable programs a little differently when it runs them. .COM files are the simpler of the two. (I speak of .EXE files a little later in this chapter.) .COM files are a simple image of the instructions and data assembled out of the source code file. When you execute a .COM program from the DOS command line, here's what happens:





  1. The .COM file is loaded into memory at a location of DOS's choosing. It doesn't change the file when it loads the file; the file is loaded exactly as it was saved to disk by the assembler.




  2. AX, BX, DX, BP, SI, and DI are set to 0.




  3. The instruction pointer IP is set to 0100H.




  4. The number of bytes loaded from disk and stored into memory is placed in the CX register.





  5. The stack pointer is set to the highest address in the program's segment, minus one.




  6. All four segment registers CS, SS, DS, and ES are set to the same value: the segment address of the single segment in which the .COM program must run. DOS chooses this value.




  7. DOS transfers control to the instruction at CS:IP, and your program is off and running!




You can see this very plainly by loading EAT.COM with DEBUG. Here's a dump of the registers immediately after loading EAT.COM into memory:




  -r
AX=0000 BX=0000 CX=001C DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000
DS=1470 ES=1470 SS=1470 CS=1470 IP=0100 NV UP EI PL NZ NA PO NC
1470:0100 BA0C01 MOV DX,010C


You'll sometimes hear the real mode flat model referred to as the Tiny model. This is a term that originated in the C programming community, which has separate names for several different arrangements of code and data, depending on whether there is a single segment for code and data or multiple segments.


The real mode flat model is simplicity itself—so simple, in fact, that it doesn't teach you much about segments. Maybe you don't need to know that much about segments to craft useful programs these days (especially in protected mode flat model), but I've found it very useful to know just how our CPUs evolved, and segments are a big part of that. So, that said, let's look at EAT.ASM crafted for the real mode segmented model.




One Program, Three Segments


The main problem with real mode flat model is that everything you do must fit into 64K of memory. This isn't much of a pinch for learning assembly language and just playing around writing small utilities, but once you try to create something ambitious-say, a word processor or database-driven e-mail client-you find that code and data begin to crowd one another in a big hurry. So, for all its trouble, real mode segmented model was the only way to make full use of real mode's megabyte of memory.


Today, of course, you'd either create a Windows application (which you would probably not attempt in assembly) or you'd work in protected mode flat model under an implementation of Unix for the Intel x86 CPUs. Nonetheless, if you understand segments, you have it in yourself to understand every other aspect of assembly work.


Let's do the Land Shark HyperBike trick again, this time with a version of EAT.ASM specifically written to use the real mode segmented model. Here's the bike-and then we'll take it apart just like last time:



  ; Source name     : EATSEG.ASM
; Executable name : EATSEG.EXE
; Code model: : Real mode segmented model
; Version : 1.0
; Created date : 9/10/1999
; Last update : 9/10/1999
; Author : Jeff Duntemann
; Description : A simple example of a DOS .EXE file programmed for
; real mode segmented model, using NASM-IDE 1.1,
; NASM 0.98, and ALINK. This program demonstrates
; how segments are defined and initialized using NASM.


[BITS 16] ; Set 16 bit code generation

SEGMENT junk ; Segment containing code

..start: ; The two dots tell the linker to Start Here.
; Note that this is a special symbol and MUST
; be in lower case! "..start:" "..START:"

; SEGMENT SETUP
;
; In real mode segmented model, a program uses three segments, and it must
; set up the addresses in the three corresponding segment registers. This
; is what the ASSUME directive does in MASM; we ASSUME nothing in NASM!
; Each of the three segments has a name (here, code, data, and stack) and
; these names are identifiers indicating segment addresses. It is the
; appropriate segment address that is moved into each segment register.
; Note that you can't move an address directly into a segment register;
; you must first move the address into a general purpose register. Also
; note that we don't do anything with CS; the ..start: label tells the
; linker where the code segment begins.

mov ax,data ; Move segment address of data segment into AX
mov ds,ax ; Copy address from AX into DS
mov ax,stack ; Move segment address of stack segment into AX
mov ss,ax ; Copy address from AX into SS

mov sp,stacktop ; Point SP to the top of the stack

mov dx,eatmsg ; Mem data ref without [] loads the ADDRESS!
mov ah,9 ; Function 9 displays text to standard output.
int 21H ; INT 21H makes the call into DOS.

mov ax, 04C00H ; This DOS function exits the program
int 21H ; and returns control to DOS.

SEGMENT data ; Segment containing initialized data

eatmsg db "Eat at Joe's!", 13, 10, "$" ;Here's our message

SEGMENT stack stack ;This means a segment of *type* "stack"
; that is also *named* "stack"! Some
; linkers demand that a stack segment
; have the explicit type "stack"

resb 64 ; Reserve 64 bytes for the program stack
stacktop: ; It's significant that this label points to
; the *last* of the reserved 64 bytes, and
; not the first!




Three Segments


Assembly language programs written for real mode segmented model must contain at least three segments: One for code, one for data, and one for the stack. Larger programs may contain more than one code segment and more than one data segment, but real mode programs may contain only one stack segment at a time.


EATSEG.ASM has those three necessary segments. Each segment has a name: stack,data, and code, which indicate pretty clearly what the segment is for. The code segment, pretty obviously, contains the machine instructions that do the program's work. The data segment contains initialized variables.



The stack segment contains the program's stack. I haven't explained stacks just yet, and because you don't really need to understand stacks in order to understand how EATSEG.ASM works, I'm going to hold off just a little while longer. In short, a stack is simply an ordered place to stash things for the short term-and that will have to do until we cover the concept in depth in the next section.


Each of the three segments is declared using the SEGMENT directive, which is a command that tells NASM that a segment begins here. The SEGMENT directive must be followed by the segment's name. You can name the segments whatever you like, but custom suggests that when you have only three segments, they be called stack,data, and code. Why obscure the meaning of what you're writing?



The segment containing the stack has some special considerations attached to it, especially regarding the linking of several files together into one executable program. One of these considerations is that the stack have the type "stack" attached to it. This tells the linker (as I explain later) that this particular segment is special-it's a stack segment and not just a data segment. Hence the line:



  SEGMENT stack stack


Nobody's stuttering here. The SEGMENT directive is creating a stack named "stack" that is of the type "stack." The first identifier is the name; the second is the type. You could change the name of the segment to MyStack or GreasedPig if you like, but it's important to let the type of the stack segment be precisely stack. More on this after we explain something else.






Don't ASSUME…


If you remember, in the real mode flat model, the operating system sets all four segment registers to the same value (one that it selects) when the program is loaded into memory and run. In the real mode segmented mode, the different segments are indeed different and distinct regions of memory and are not all the same place. When the program begins running, DOS doesn't set the segment registers to anything. Your program must do that on its own. (DOS does, of course, set CS to the start of the code segment before giving control to your program. The other segment registers it leaves alone.)


This is what the first part of EATSEG.ASM does: It takes the addresses represented by the segment names for the data and stack segments and loads them into DS and SS, the segment registers governing those segments:



  mov  ax,data     ; Move segment address of data segment into AX
mov ds,ax ; Copy address from AX into DS
mov ax,stack ; Move segment address of stack segment into AX
mov ss,ax ; Copy address from AX into SS


Keep in mind that you can only load a segment register from a general-purpose register-you can't load it from anything else, either immediate data or memory data. This is why the segment addresses have to pass through AX to get into DS and SS. (Because we're not using ES to govern a segment defined at assembly time right there in our program, we don't need to load ES with anything right off the bat.)



This is a good place to point out a crucial difference between NASM (the assembler that we're using in this book) and Microsoft's extremely popular MASM, which is probably the most-used assembler in history: MASM attempts to associate segment names with segment types. NASM does not.


With one small exception done as a courtesy to the linker, NASM does not know which segment is the code segment, nor which segment is the data segment, nor which segment is the stack segment. You define a segment by name:



  SEGMENT data       ; Segment containing initialized data



The name "data," however, tells you that it's the data segment. The assembler doesn't look for the string "data" and note somewhere that the segment named data is the data segment. This is why you could change the preceding line to this:




  SEGMENT GreasedPig ; Segment containing initialized data


Nothing would change. GreasedPig is an odd name for a segment, but a completely legal one.


In MASM, Microsoft defines the ASSUME directive, which associates segment names with segment registers. This allows MASM to generate segment prefixes automatically when it creates the opcodes called out by a particular mnemonic in your source code. This is a tricky and subtle business, so to make this clearer, imagine a memory variable defined in a segment that is addressed via ES:



      SEGMENT JunkSegment
JunkChunk DW 0FFA7H



At the beginning of the program, you have to make sure ES is loaded with the segment address of JunkSegment:



  MOV AX, JunkSegment ; Load segment address of JunkSegment into ES via AX
MOV ES, AX


Ordinarily, using NASM, you have to specify when a piece of memory data is located relative to the ES register, because the default is DS:



  MOV AX,[ES:JunkChunk] ; Move word variable JunkChunk from JunkSegment (ES) into AX



That's the NASM way. Using Microsoft's MASM, you can associate a segment name with ES using the ASSUME directive:



  ASSUME ES:JunkSegment


Having associated ES and JunkSegment this way, you could now write the MOV instruction without explicitly including the ES: segment prefix:




  MOV AX,[JunkChunk] ; Move word variable JunkChunk from JunkSegment (ES) into AX


Thanks to ASSUME, MASM knows that the variable JunkChunk is located in extra segment ES, so it inserts the ES: prefix behind the scenes as it generates the opcode for this mnemonic. Many of us (NASM's authors included) don't think this is a particularly good idea. It makes the source code less specific and hence less readable-a person not familiar with the program might assume (heh-heh) that JunkChunk is in the data segment associated with DS because there's no ES: prefix and DS is the default for memory variable references like that.


So, NASM has nothing like ASSUME. When you move away from the default addressing of memory variables relative to DS, you must include the segment register prefix inside the square brackets of all memory variable references!






Naming the Stack Segment


The exception I noted earlier is that NASM allows you to say which segment is the stack segment:



  SEGMENT MyStack stack


Here, MyStack is the name of the segment (which can be any legal identifier) and stack is the type. This is not for NASM's benefit-it will not take any action of its own based on knowing that the segment named MyStack is in fact the stack segment. But some linkers need to know that there is a stack segment defined in the program. Stack segments are special as segments go, at least in part (kind of like Tigger) there can be only one-but there must be one! Some linkers check to see whether there is a segment in a program designated as the stack segment, and to keep such linkers quiet NASM allows you to give the stack type to a segment defined with SEGMENT.



This is a good idea and I recommend that you do it.






Choosing a Starting Point


There are no jumps, loops, or subroutines in EATSEG.ASM. If you've a smattering of assembly language smarts you may wonder if the ..start: label at the beginning of the code segment is unnecessary except for readability purposes. After all, start is not referenced anywhere within the program.


On the other hand, code execution has to begin somewhere, and you need to tell the assembler (and especially the linker) where code execution must begin. This is the purpose of the ..start: label.



The issue is this: DOS needs to know at what address to begin execution when it loads and runs the program. (DOS sets code segment register CS when it loads your program into memory prior to executing it.) You might think DOS could assume that execution would begin at the start of the code segment, but there may be more than one code segment, and under most circumstances the programmer does not specify the order of multiple code segments within a single program. (The linker has the power to rearrange multiple code segments for reasons that I can't explain in this book.) Better to have no doubt about it, and for that reason you the programmer should pick a starting point and tell the assembler what it is.


You may notice that leaving out ..start: won't keep NASM from assembling a program, and while the linker will complain about the lack of a starting point, the linker will default to starting execution at the beginning of the code segment-which in our case is the only code segment, so there's no ambiguity there.


Nonetheless, it's bad practice to leave out the starting point label.







Assembling and Linking EATSEG.ASM


Although NASM can generate a .COM file (for a real mode flat model program) directly, it can't generate a .EXE file for a real mode segmented model program in the same way. Once you move away from a single segment in real mode flat model, NASM needs the help of a linker to generate the final .EXE file.


I've obtained permission to distribute an excellent free linker with this book's CD-ROM. The linker is ALINK, written by Anthony Williams. It's on the CD-ROM, and if you copied the executable file ALINK.EXE to your hard drive along with everything else, you can invoke it simply by naming it.


NASM-IDE was intended for writing programs in real mode flat model, so it relies exclusively on NASM and does not have any machinery for invoking a linker. That means that NASM-IDE won't be able to do the assemble and link tasks for us. It's time to face the fiendish command line.


If you're working from DOS you can simply assemble and link from the DOS command line. If you're working with NASM-IDE in a DOS box under Windows, it's probably easier to "shell out" to the DOS command line from inside NASM-IDE. This is done by selecting the menu item File|DOS Shell. You will see NASM-IDE vanish and be replaced by a blank screen with the DOS prompt. When you're done with the DOS shell, type EXIT followed by Enter to return to NASM-IDE.


Assembling EATSEG.ASM is done with the following command line:



  C:\>NASM16 EATSEG.ASM -f obj -o EATSEG.OBJ




This command line will assemble EATSEG.ASM to the file EATSEG.OBJ, in the standard .OBJ linkable file format. Linking is even easier:



  C:\>ALINK EATSEG.OBJ


Here, ALINK will convert EATSEG.OBJ into EATSEG.EXE. I explain more about linkers and what they do in the next chapter. Here, ALINK is acting more as a file format converter than anything else, since there's only one file to be linked. Later on, we'll see how ALINK can connect multiple .OBJ files into a single executable file.


After ALINK runs, you'll have the file EATSEG.EXE on your hard disk. That's the file that you can name at the DOS command line to run EATSEG.




Last In, First Out via the Stack


One problem with assembly language is that it's tough knowing where to put things. There are only so many registers to go around. Having variables in a data segment is helpful, but it isn't the whole story. People who come to assembly from higher-level languages such as Pascal and Basic find this particularly jarring, since they're used to being able to create new variables at any time as needed.


The x86 CPUs contain the machinery to create and manage a vital storage area called the stack. The name is appropriate, and for a usable metaphor I can go back to my high school days, when I was a dishwasher for Resurrection Hospital on Chicago's Northwest Side.




Five Hundred Plates an Hour


What I did most of the time was pull clean plates from a moving conveyor belt of little prongs that emerged endlessly from the steaming dragon's mouth of a 180° dishwashing machine. This was hot work, but it was a lot less slimy than stuffing the dirty plates into the other end of the machine.



When you pull 500 plates an hour out of a dishwashing machine, you had better have some place efficient to stash them. Obviously, you could simply stack them on a table, but stacked ceramic plates in any place habituated by rowdy teenage boys is asking for fragments. What the hospital had instead was an army of little wheeled stainless steel cabinets equipped with one or more spring-loaded circular plungers accessed from the top. When you had a handful of plates, you pushed them down into the plunger. The plunger's spring was adjusted such that the weight of the added plates pushed the whole stack of plates down just enough to make the new top plate flush with the top of the cabinet.


Each plunger held about 50 plates. We rolled one up next to the dragon's mouth, filled it with plates, and then rolled it back into the kitchen where the clean plates were used at the next meal shift to set patients' trays.


It's instructive to follow the path of the first plate out of the dishwashing machine on a given shift. That plate got into the plunger first and was subsequently shoved down into the bottom of the plunger by the remaining 49 plates that the cabinet could hold. After the cabinet was rolled into the kitchen, the kitchen girls pulled plates out of the cabinet one by one as they set trays. The first plate out of the cabinet was the last plate in. The last plate out of the cabinet had been the first plate to go in.


The x86 stack is like that. We call it a last in, first out, or LIFO stack.






An Upside-Down Segment


Two of the x86 registers team up to create and maintain the stack. Like everything else in 86-land, the stack must exist within a segment. The SS (Stack Segment) register holds the segment address of the segment chosen to be the stack segment, and the SP (Stack Pointer) register points to locations within the stack segment. As with all other segments in real mode, the stack segment may be as much as 65,536 bytes long, but it may be any length less than that as well. You'll find in practice that the stack rarely needs to be larger than a thousand bytes or so unless you're doing some really peculiar things.


The stack segment begins at SS:0, but the truly odd thing about it is that all the stack action happens at the opposite end of the stack segment. When a stack segment is set up, the SS register points to the base or beginning of the stack segment, and SP is set to point to the end of the stack segment. To store something in the stack segment (which we usually call "pushing something onto the stack"), we move SP "down the stack" (that is, closer to SS) and then copy the item to the memory location pointed to by SS:SP.



This takes some getting used to. Figure 8.1 provides the big picture of the stack segment and the two pointers that give it life. In real mode flat model, SS is set to the base of the stack segment by DOS when the program is loaded and begins running. (And all the other segment registers are set to the same address.) In real mode segmented model, you set SS from the address of the segment that you define within the program in two steps, first using NASM's SEGMENT directive:



Click To expand

Figure 8.1: The big picture of the real mode stack.


  SEGMENT stack stack



Then you need a couple of MOV instructions to get the address of segment stack into SS:




  mov  ax,stack     ; Move segment address of stack segment into AX
mov ss,ax ; Copy address from AX into SS


Defining a stack segment just provides a starting point address for that segment. No room is actually reserved for the stack by the SEGMENT directive. That requires a new directive that we haven't discussed:




  resb 64      ; Reserve 64 bytes for the program stack



RESB means "REServe Byte." And it means just that: It tells the assembler to set aside 64 bytes starting at the beginning of the stack segment and not to let anything else (such as memory variables) be defined in that reserved space. You can use RESB to reserve as much stack as you think you'll need; 64 bytes is enough for simple experimentation. If you're writing a more ambitious program, you may be better off looking at what it does and actually estimating a worst-case demand for stack space.


Note that you don't need to use RESB to reserve stack space if you're working in real mode flat model. The stack in that model exists at the very highest addresses of the single segment the program lives in. The space isn't reserved in the strictest sense, and you have to be careful not to let your code or data get so high in memory that it collides with your stack. This is called a stack crash and you're not likely to see one in your own programs until you get a lot further along in your assembly experience.



SP is set to the far (that is, the high, address-wise) end of the stack segment. (See Figure 8.1, where an arrow indicates the initial value of SP.) Again, if you're working in real mode flat model, DOS does it when your program is loaded—as you can see if you load EAT.COM with DEBUG and display the registers with the R command. SP will have a value something like 0FFFEH—in any case, something fairly high rather than close to 0000H.


And if you're working in real mode segmented model, you have to set SP yourself. This is done by first indicating the initial address to be contained in SP:



       resb 64    ; Reserve 64 bytes for the program stack
stacktop: ; It's significant that this label points to
; the *last* of the reserved 64 bytes, and
; not the first!


Note that the label stacktop: is immediately after the RESB 64 directive. The label stacktop: represents an address at the very end of the block of reserved memory locations set aside by RESB. Although the position of the two lines on the source code listing suggests that stacktop: points beyond the block of memory set aside by RESB, that's not the case. The stacktop: label resolves to the offset of the last byte in that block of 64 bytes.



You load the address represented by the stacktop: label into SP when the program begins, typically right after you set up the segment registers:



  mov  sp,stacktop    ; Point SP to the top of the stack


After that's set up, you have valid values in both SS and SP, and you can begin using the stack.






Pushing Data



You can place data onto the stack in numerous ways, but the most straightforward way involves a trio of related machine instructions, PUSH, PUSHF, and PUSHA. The three are similar in how they work, and differ as to what they push onto the stack. PUSHF pushes the Flags register onto the stack. PUSHA pushes all eight of the 16-bit general-purpose registers. PUSH pushes a 16-bit register or memory value that is specified by you in your source code, like so:




  PUSHF     ; Push the Flags register
PUSHA ; Push AX, CX, DX, BX, SP, BP, SI, and DI, in that order, all at once
PUSHAD ; Push EAX, ECX, EDX, EBX, ESP, ESP, EBP, ESI, and EDI, all at once
PUSH AX ; Push the AX register
PUSH [BX] ; Push the word stored in memory at DS:BX
PUSH DI ; Push the DI register
PUSH ES ; Push the ES register


Note that PUSHF takes no operands. You'll generate an assembler error if you try to hand it an operand; PUSHF pushes the flags and that's all it is capable of doing.



PUSH and PUSHF work this way: First SP is decremented by one word (two bytes) so that it points to an empty area of the stack segment that is two bytes long. Then whatever is to be pushed onto the stack is written to memory in the stack segment at the offset address in SP. Voila! The data is safe on the stack, and SP has crawled two bytes closer to SS. We call the word of memory pointed to by SP the top of the stack.




PUSHA works the same way, except that it pushes eight 16-bit registers at once, thus using 16 bytes of stack space at one swoop. One thing to remember is that PUSHA is a newer instruction that doesn't exist on the 8086 and 8088. It first appeared with the 286.



PUSHAD was added with the 386, and it pushes all eight 32-bit general-purpose registers onto the stack in one blow.


All memory between SP's initial position and its current position (the top of the stack) contains real data that was explicitly pushed on the stack and will presumably be fetched from the stack (we say popped from the stack) later on. In real mode segmented model, the stack exists in a separate segment, and memory between SS and SP is considered free and available and is used to store new data that is to be pushed onto the stack. This is not the case in real mode flat model, where the stack shares the same segment that everything else in the program is using.



What can and cannot be pushed onto the stack is complicated and depends on what CPU you're using. None of the x86 CPUs can push 8-bit registers onto the stack. You can't push AL or BH or any other of the 8-bit registers. Segment registers and 32-bit extended general-purpose registers can be pushed in real mode, assuming you have a 386 or later CPU. Similarly, immediate data can be pushed onto the stack, but only if you have a 286 or later CPU. Keeping track of all this used to be a problem, but you're unlikely to be running code on CPUs earlier than the 386 these days.


Your morbid curiosity may be wondering what happens when SP runs out of room in its downward crawl and collides with SS. Nothing good, certainly—it depends heavily on how your program is laid out—but I would lay money on your program crashing hard and possibly taking the system down with it. (If you're working in a DOS box under Windows NT you at least won't crash the operating system. All bets are off for Windows 9x!)



Stack crashes are serious business, at least in part because there is only one stack in action at a time in real mode. It's a little hard to explain (especially at this stage in our discussion), but this means that the stack you set up for your own program must be large enough to support as well the needs of DOS and any interrupt-driven code (typically in the BIOS) that may be active while your program is running. Even if you don't fully understand how someone else may be using your program's stack at the same time you are, give those other guys some extra room—and keep an eye on the proximity of SS and SP while you trace a program in DEBUG.






POP Goes the Opcode



In general, what gets pushed must get popped, or you can end up in any of several different kinds of trouble. Getting a word of data off the stack is done with another trio of instructions, POP, POPF, andPOPA. As you might expect, POP is the general-purpose one-at-a-time popper, while POPF is dedicated to popping the flags off of the stack. POPA pops 16 bytes off the stack into the eight general-purpose 16-bit registers. POPAD is the flip side of PUSHAD and pops the top 32 bytes off the stack into the eight general-purpose 32-bit registers.




  POPF     ; Pop the top of the stack into Flags
POPA ; Pop the top 16 bytes from the stack into AX, CX, DX, BX, SP,
; BP, SI, and DI
POPAD ; Pop the top 32 bytes into EAX, ECX, EDX, EBX, ESP, ESP, EBP,
; ESI, and EDI
POP SI ; Pop the top of the stack into SI
POP CS ; Pop the top of the stack into CS
POP [BX] ; Pop the top of the stack into memory at DS:BX


As with PUSH, POP only operates on word-sized operands. Don't try to pop data from the stack into an 8-bit register such as AH or CL.



POP works pretty much the way PUSH does, but in reverse: First the word of data at SS:SP is copied from the stack and placed in POP's operand, whatever you specified that to be. Then, SP is incremented (rather than decremented) by two bytes, so that in effect it moves two bytes up the stack, away from SS.



It's significant that SP is decremented before placing a word on the stack at push time, but incremented after removing a word from the stack at pop time. Certain other CPUs work in the opposite manner, which is fine—just don't get confused. Unless the stack is empty, SP points to real data, not empty space.


Ordinarily, you don't have to remember that fact, as PUSH and POP handle it all for you and you don't have to manually keep track of what SP is pointing to. If you decide to manipulate the stack pointer directly, it helps to know the sequence of events behind PUSH and POPand that's an advanced topic that I won't be going into in this book.




Figure 8.2 shows the stack's operation in a little more detail. The values of the four "X" registers at some hypothetical point in a program's execution are shown at the top of the figure. AX is pushed first on the stack.




Click To expand

Figure 8.2: How the stack works.

Its least significant byte is at SS:SP, and its most significant byte is at SS:SP+1. (Remember that both bytes are pushed onto the stack at once, as a unit!)


Each time one of the registers is pushed onto the stack, SP is decremented two bytes down toward SS. The first three columns show AX, BX, and CX being pushed onto the stack, respectively. But note what happens in the fourth column, when the instruction POP DX is executed. The stack pointer is incremented by two bytes and moves away from SS. DX now contains a copy of the contents of CX. In effect, CX was pushed onto the stack, and then immediately popped off into DX.



That's a roundabout way to copy the value of CX into DX. MOV DX,CX is lots faster and more straightforward. However, MOV will not operate on the Flags register. If you want to load a copy of Flags into a register, you must first push the Flags register onto the stack with PUSHF, then pop the flags word off the stack into the register of your choice with POP. Getting the Flags register into BX is done like this:



  PUSHF  ; Push the flags register onto the stack..
POP BX ; ..and pop it immediately into BX







Storage for the Short Term


The stack should be considered a place to stash things for the short term. Items stored on the stack have no names, and in general must be taken off the stack in the reverse order that they were put on. Last in, first out, remember. LIFO!


One excellent use of the stack allows the all-too-few registers to do multiple duty. If you need a register to temporarily hold some value to be operated on by the CPU and all the registers are in use, push one of the busy registers onto the stack. Its value will remain safe on the stack while you use the register for other things. When you're finished using the register, pop its old value off the stack—and you've gained the advantages of an additional register without really having one. (The cost, of course, is the time you spend moving that register's value onto and off of the stack. It's not something you want to do in the middle of an often-repeated loop!)




Using DOS Services through INT


I think of EAT.ASM as something of a Tom Sawyer program. It doesn't do much, and it does what it does in time-honored Tom Sawyer fashion—by getting somebody else to do all the work. All that EAT does is display a character string on your screen. The visible part of that string is the advertising slogan itself: Eat at Joe's! The other part is the pair of invisible characters we call newline or EOL: carriage return (0DH) followed by line feed (0AH). (For more on EOL markers and how they interact with text, see Chapter 4.) The EOL marker does nothing more than return the display cursor to the left margin of the next screen line, so that any subsequent text displayed will begin at the left margin and not nipping at the heels of Eat at Joe's!


Both parts of our advertising slogan are sent to the display at once, and via the same mechanism: through a DOS service.


As I explain in Chapter 4, DOS is both a god and a troll. It controls all the most important elements of the machine in godlike fashion: the disk drives, the printer, and (to some extent) the display. At the same time, DOS is like a troll living under a bridge to all those parts of your machine: You tell the troll what you want done, and the troll will go out and do it for you.


There is another troll guarding the bridges to other components of your machine called the BIOS, to which we'll return in a little while. DOS and BIOS both offer services, which are simple tasks that your programs would have to do themselves if the services were not provided. Quite apart from saving you the programmer a lot of work, having DOS and BIOS services helps guarantee that certain things will be done in identical fashion on all machines, which (especially in terms of disk storage) is a major reason software written for DOS runs on so many different machines: All the machine-dependent stuff is done the same way.


One of the services DOS provides is simple (far too simple, actually) access to your machine's display. For the purposes of EAT.ASM (which is just a lesson in getting your first assembly language program written and operating), simple services are enough.


So—how do we use DOS and BIOS services? The way is as easy to use as it is tricky to understand: through software interrupts.





An Interrupt That Doesn't Interrupt Anything


As one new to the x86 family of processors back in 1981, the notion of a software interrupt drove me nuts. I kept looking and looking for the interrupter and interruptee. Nothing was getting interrupted.


The name is unfortunate, even though I admit that there was some reason for calling software interrupts what they are. They are, in fact, courteous interrupts—if you can still call an interrupt an interrupt when it is so courteous that it does no interrupting at all.


The nature of software interrupts and DOS services is best explained by a real example illustrated twice in EAT.ASM. As I hinted previously, DOS keeps little sequences of machine instructions tucked away within itself. Each sequence does something useful—read something from a disk file, display something to the screen, send something to the printer. DOS uses them to do its own work, and it also makes them available (with its troll hat on) to you the programmer to access from your programs.


Well, there is the critical question: How do you find something tucked away inside of DOS? All code sequences, of course, have addresses, and Microsoft or IBM could publish a booklet of addresses indicating where all the code is hidden. There are numerous good reasons not to pass out the addresses of the code itself, however. DOS is evolving and (we should hope) being repaired on an ongoing basis. Repairing and improving code involves adding, changing, and removing machine instructions, which changes the size of those hidden code sequences—and also, in consequence, changes their location. Add a dozen instructions to one sequence, and all the other sequences up-memory from that one sequence will have to shove over, to make room. Once they shove over, they'll be at different addresses, so instantly the booklets are obsolete. Even one byte added to or removed from a code sequence in DOS could change everything. What if the first code sequence has a bug that must be fixed?



The solution is ingenious. At the very start of real mode memory, down at segment 0, offset 0, is a special table with 256 entries. Each entry is a complete address including segment and offset portions, for a total of 4 bytes per entry. The first 1,024 bytes of memory in any x86 machine are reserved for this table, and no code or data may be placed there.


Each of the addresses in the table is called an interrupt vector. The table as a whole is called the interrupt vector table. Each vector has a number, from 0 to 255. The vector occupying bytes 0 through 3 in the table is vector 0. The vector occupying bytes 4 through 7 is vector 1, and so on, as shown in Figure 8.3.



Click To expand

Figure 8.3: The interrupt vector table.


None of the addresses is burned into permanent memory the way BIOS routines are. When your machine starts up, DOS and BIOS fill many of the slots in the interrupt vector table with addresses of certain service routines within themselves. Each version of DOS knows the location of its innermost parts, and when you upgrade to a new version of DOS, that new version will fill the appropriate slots in the vector table with upgraded and accurate addresses.



What doesn't change from DOS version to DOS version is the number of the interrupt that holds a particular address. In other words, since the PC first began, interrupt 21H has pointed the way into darkest DOS to DOS's services dispatcher, a sort of multiple-railway switch with spurs heading out to the many (over 50) individual DOS service routines. The address of the dispatcher has changed with every DOS version, but regardless of version, programs can find the address of the dispatcher in slot 21H of the interrupt vector table.


Furthermore, programs don't have to go snooping the table for the address themselves. The x86 CPUs include a machine instruction that makes use of the interrupt vector table. The INT (INTerrupt) instruction is used by EAT.ASM to request the services of DOS in displaying two strings on the screen. At two places, EAT.ASM has an INT 21H instruction. When an INT 21H instruction is executed, the CPU goes down to the interrupt vector table, fetches the address from slot 21H, and then jumps execution to the address stored in slot 21H. Since the DOS services dispatcher lies at the address in slot 21H, the dispatcher gets control of the machine and does the work that it knows how to do.



The process is shown in Figure 8.4. When DOS loads itself at boot time, one of the many things it does to prepare the machine for use is put correct addresses in several of the vectors in the interrupt vector table. One of these addresses is the address of the dispatcher, which goes into slot 21H.



Click To expand

Figure 8.4: Riding the interrupt vector into DOS.

Later on, when you type the name of your program MYPROG on the DOS command line, DOS loads MYPROG.EXE into memory and gives it control of the machine. MYPROG.EXE does not know the address of the DOS dispatcher. MYPROG does know that the dispatcher's address will always be in slot 21H of the vector table, so it executes an INT 21 instruction. The correct address lies in vector 21H, and MYPROG is content to remain ignorant and simply let the INT 21 instruction and vector 21H take it where it needs to go.



Back on the Northwest Side of Chicago, where I grew up, there was a bus that ran along Milwaukee Avenue. All Chicago bus routes had numbers, and the Milwaukee Avenue route was number 56. It started somewhere in the tangled streets just north of Downtown, and ended up in a forest preserve just inside the city limits. The Forest Preserve District ran a swimming pool called Whelan Pool in that forest preserve. Kids all along Milwaukee Avenue could not necessarily have told you the address of Whelan Pool. But come summer, they'd tell you in a second how to get there: Just hop on bus number 56 and take it to the end of the line. It's like that with software interrupts. Find the number of the vector that reliably points to your destination, and ride that vector to the end of the line, without worrying about the winding route or the address of your destination.



Note that the INT 21 instruction does something else: It pushes the address of the next instruction (that is, the instruction immediately following the INT 21 instruction) on the stack before it follows vector 21H into the depths of DOS. Like Hansel and Gretel, the INT 21 was pushing some breadcrumbs to the stack as a way of helping execution find its way back to MYPROG.EXE after the excursion down into DOS—but more on that later.



Now, the DOS dispatcher controls access to dozens of individual service routines. How does it know which one to execute? You have to tell the dispatcher which service you need, and you do so by placing the service's number in 8-bit register AH. The dispatcher may require other information as well and will expect you to provide that information in the correct place before executing INT 21.


Look at the following three lines of code from EAT.ASM:



  mov  dx,eatmsg   ; Mem data ref without [] loads the ADDRESS!
mov ah,09H ; Function 9 displays text to standard output.
int 21H ; INT 21H makes the call into DOS.


This sequence of instructions requests that DOS display a string on the screen. The first line sets up a vital piece of information: the offset address of the string to be displayed on the screen. Without that, DOS will not have any way to know what it is that we want to display. The dispatcher expects the offset address to be in DX and assumes that the segment address will be in DS.


In flat model, DS is initialized by DOS at execution time. In segmented model, the address of the data segment was loaded into DS earlier in the program by these two instructions:



  mov  ax,data    ; Move segment address of data segment into AX
mov ds,ax ; Copy address from AX into DS



Once loaded, DS is not disturbed during the full run of the program, so the DOS dispatcher's assumption is valid even though DS is loaded at the start of program execution and not each time we want to display a string.


In moving 09H into register AH, we tell the dispatcher which service we want performed. Service 09H is DOS's Print String service. This is not the fastest nor in other ways the best way to display a string on the PC's screen, but it is most certainly the easiest.


DOS service 09H has a slightly odd requirement: That the end of the string be marked with a dollar sign ($). This is the reason for the dollar sign hung incongruously on the end of EAT.ASM's advertising slogan string. Given that DOS does not ask us to pass it a value indicating how long the string is, the end of the string has to be marked somehow, and the dollar sign is DOS's chosen way. It's a lousy way, unfortunately, because with the dollar sign acting as a marker, there is no way to display a dollar sign. If you intend to talk about money on the PC's screen, don't use DOS service 9! As I said, this is the easiest, but certainly not the best way to display text on the screen.


With the address of the string in DS:DX and service number 09H in AH, we take a trip to the dispatcher by executing INT 21H. The INT instruction is all it takes—boom!, and DOS has control, reading the string at DS:DX and sending it to the screen through mechanisms it keeps more or less to itself.






Getting Home Again


So much for getting into DOS. How do we get home again? The address in vector 21H took control into DOS, but how does DOS know where to go to pass execution back into EAT.EXE? Half of the cleverness of software interrupts is knowing how to get there, and the other half—just as clever—is knowing how to get back.


To get into DOS, a program looks in a completely reliable place for the address of where it wants to go: the address stored in vector 21H. This address takes execution deep into DOS, leaving the program sitting above DOS. To continue execution where it left off prior to the INT 21 instruction, DOS has to look in a completely reliable place for the return address, and that completely reliable place is none other than the top of the stack.


I mentioned earlier (without much emphasis) that the INT 21 instruction pushes an address to the top of the stack before it launches off into the unknown. This address is the address of the next instruction in line for execution: the instruction immediately following the INT 21 instruction. This location is completely reliable because, just as there is only one interrupt vector table in the machine, there is only one stack in operation at any one time. This means that there is only one top of the stack—that is, SS:SP—and DOS can always send execution back to the program that called it by popping the address off the top of the stack and jumping to that address.



The process is shown in Figure 8.5, which is the continuation of Figure 8.4. Just as the INT instruction pushes a return address onto the stack and then jumps to the address stored in a particular vector, there is a




Click To expand

Figure 8.5: Returning home from an interrupt.


"combination" instruction that pops the return address off the stack and then jumps to the address. The instruction is IRET (for Interrupt RETurn), and it completes this complex but reliable system of jumping toan address when you really don't know the address. The trick, once again, is knowing where the address can reliably be found. (There's actually a little more to what the software interrupt mechanism pushes onto and pops from the stack, but it happens transparently enough that I don't want to complicate the explanation at this point—and you're unlikely to be writing your own software interrupt routines for a while.)


This should make it clear by now what happens when you execute an INT 21 instruction. EAT.ASM uses DOS services to save it the trouble of writing its string data to the screen a byte at a time. The address into DOS is at a known location in the interrupt vector table, and the return address is at a known location on the stack. Whereas I've described the software interrupt system in terms of the DOS service dispatcher interrupt 21H, the system is precisely the same for all other software interrupts—and there are many. In the next chapter we use a few more and explore some of the many services available through the BIOS interrupts that control your video display and printer.







Software Interrupts versus Hardware Interrupts


Software interrupts evolved from an older mechanism that did involve some genuine interrupting: hardware interrupts. A hardware interrupt is your CPU's mechanism for paying attention to the world outside itself.


There is a fairly complex electrical system built into your PC that allows circuit boards to send signals to the CPU. An actual metal pin on the CPU chip is moved from one voltage level to another by a circuit board device like a disk drive controller or a serial port board. Through this pin, the CPU is tapped on the shoulder by the external device. The CPU recognizes this tap as a hardware interrupt. Like software interrupts, hardware interrupts are numbered, and for each interrupt number there is a slot reserved in the interrupt vector table. In this slot is the address of an interrupt service routine (ISR) that performs some action relevant to the device that tapped the CPU on the shoulder. For example, if the interrupt signal came from a serial port board, the CPU would then allow the serial port board to transfer a character byte from itself into the CPU.



Most properly, any routine that lies at the end of a vector address in the interrupt vector table is an ISR, but the term is usually reserved for hardware interrupt service routines.


The only difference between hardware and software interrupts is in the event that triggers the trip through the interrupt vector table. With a software interrupt the triggering event is part of the software; that is, an INT instruction. With a hardware interrupt, the triggering event is an electrical signal applied to the CPU chip itself without any INT instruction taking a hand in the process. The CPU itself pushes the return address on the stack when it recognizes the electrical pulse that triggers the interrupt; however, when the ISR is done, a RET instruction sends execution home, just as it does for a software interrupt.


Hardware ISRs can be (and usually are) written in assembly language. It's a difficult business, because the negotiations between the hardware and software must be done just so, or the machine may lock up or go berserk. This is no place for beginners, and I would advise you to develop some skill and obtain some considerable knowledge of your hardware setup before attempting to write a hardware ISR.



Wednesday, July 9

Why oh why?

Now what is this COD4. Hope it is not a game. Hope its not a game or I will have to fight myself to remain happy in Commandos@Wikipedia.org, Secret Agent Clank@Wikipedia.org and Go Sudoku and not to test this game just because myBooboo is/might be playing it.

Man.

What to do.

([^_^])

So happy

So happy to see vVaa has updated her Flickr page. So happy. So much happy. Don't know why but I am so happy. Exploding. A blessing this heart is. Wow. Thanks. Thanks. Thanks. Love you. OMG! So happy. Thanks. Thanks you all. So much. So much.

([^_^])

Now listening to All That I Am by Rob Thomas

Saturday, July 5

There will be no more posts on this blog.

Please visit my other blog for what I am writing.

Thanks for visiting.

There should be an option on websites that says

Are you using Windows because you can't buy Mac OS?, for their whose-using-what data.

OK! may be some AI thing that automatically knows what the feelings are of the user towards the OS he or she are using.

I mean I am using Windows XP and want to use Mac OS but am not allowed to.

Was watching V's Tekzilla and there they were talking about what their viewers use as operating system. And I was like 'God, they think I am a Windows user', shit.

([^_^])

Watching and reading about Bruce

And during going through those photos I saw photos of some beautiful ladies(there were men too :) in the media and started reading about them(there was a reason for me to do that) and once done reading that one page, I started LOL that if Bruce found out that rather than watching his photos, I was watching photos of other people, what would he say?

LOL.

Bruce, what would you say and I read how you keep your daughters safe. Man thats crazy.

Cool.

Now listening to Jazba E Junoon by Junoon

Friday, July 4

Some vGame

And then sleep.

And Booba, don't know how to come to you and not let you go away from me. Just don't know how. Booba. But can imagine you and that I do all the time. Love you yaraa, love you, piyaaree.

([^_^])

Now listening to Tairee Yeh Chunariya from Hello Brother

Ate food and loved it

My mother roasted six pieces of chicken and a paratha and I ate all of that with 1/5 of one of the sandwiches. The other one I gave it to my mother and my sister.

Now I am going to watch Arnie's Predator(Arnie, what would it be when we would meet. Man, I would be hugging you and shaking you hand. WOW! Lucky me -placed the closing bracket here but didn't felt I did justice- Lucky me) and would enjoy the icecreams and the softdrink.

Love you all.

Miss you Booboo. Miss you a lot. Love you.

Now listening to Gila gila by Adnan Sami Khan

Downloading Quicktime Pro 7.5

How about Cammadno 2 animated based on Commando game.

([^_^])

Going to sleep.

Have been sleeping all morning and will sleep more.

But before I could sleep again I wanted to know if there is an another game coming in Commando series by Eidos/Pyro.

Don't know but found out that they have changed the format of the game in the last version of Commando where rather than third person overhead view style, it has become the first person shooter game.

At first I was like why would they do that but if they didn't changed anything else and we can still play the game as we did the rest of them, then I guess I would still be in love with it.

Love Commando.

And during my reading, I remembered Arnie's film Commando and was wondering what if Eidos/Pyro made the sixth iteration of Commando and there after, with Arnie as the leader of the team. Like talking to you, briefing you, has all the cool tactics and weapons and could also hack. Now that thought was Owesome.

But there is no news on if there will be a sixth Commando or not.

Sad.

([^_^])

I wanted to download another episode of Tekzilla-Due-2-V but found out that I have download the previous one again which got downloaded to more than 80% and now can't continue because it was stopped due to power outage. I was downloading the Flash version as not all other formats either play or can be edited the way I edit videos.

I keep calling myBooboos, Boobaas.

Soulo. Mano. vVoo.

Now listening to Allah Ho from Khuda Kay Liay

Thursday, July 3

What to say about TIH. All Owesomeness aside, while I was enjoying it the third time, was also enjoying like a first timer.

Its a blessing and to see this blessing and to enjoy it is also a blessing.

There were those near me who were seeing it for the first time and scenes that were still great if not greatest like other scenes and were three times old, their comments were taking me back to how I first enjoyed these scenes.

So while I was enjoying other details that I missed the first and second time, I was enjoying living the experience of seeing TIH for the first time.

Owesome.

When I woke up in the evening, I didn't had enough motivation to go and watch TIH. I was in bed, still sleepy. But started imaging the scenes from TIH and told myself that I love it, would love it again and if I didn't saw it this time, I will regret it. And slowly I woke up fully and soon was blessed and out of the door.

Then I wanted to buy Hulk@Wikipedia.org but I was getting late as my mother had ordered a cake and some pizzas for my birthday.

Back home, I intentionally ate more than I should had. Wanted to make her happy and enjoy the family time together.

Liked it.

Also my niece and my sister called me from Peshawar. They are coming to Karachi on 8th. That would be fun. Will be here for almost two months. My these -- oh! Peshawar, thats were Sara lives, my God the memory just suddenly stopped me from existing on this chair of mine and I felt like exploding with happiness in the heavens above :) -- nieces and nephews from Peshawar are so mischievous ([^_^]) One of them will just pick up a bag, any bag and will fill it with what ever she likes. Yes things are going to get lost and found. So I can either worry or just let it be with little caution. I choose the later. And then there are those two who like to rUmbLE. God help us all.

([^_^])

I am tired.

But want to investigate Hitler and Jews. Read about image formats. Download V's videos from Tekzilla. Want to hear from iMan. Want find a way to know how they created TIH and other movies, may be mailing list, not sure yet.

Goodnight. Will sleep now and thanks for giving me the energy to wake from sleep and to go about reinstalling a day into my life. I love you. I love you all.

Thanks.

Just had a more than two hour sleep and feel refreshed

Going to go and see TIH the last time in Cinema. I thought I won't be able to get up in time and won't be that refreshed but I am, so why not. Its my birthday.

Hehe!

([^_^])

Only few sips of Pepsi so far

Have been playing Secret Agent Clank and God Of War.

I love God Of War. The warrior in me loves all that action. The chains and the swords, wow! To be powerful in the face of your enemy, Owesome. Loved it. Will keep playing it. Its a demo and soon I will get the God Of War: Chain Of Olympus.

I can download the Chain Of Olympus right now but I have this one so lets finish this and then move on. No need to stop this and start waiting for another. So lets first finish this demo and then we will see where It takes me.

([^_^])

Received my Firefox Download Day Certificate for helping Mozilla secure a world record for the most software downloads in 24 hours. 8,002,530 downloads. Check you eMail, there is one for you too. :)

Will now surf for news or images.

([^_^])
Now listening to Maira Sajan Aa Giya from Zinda Dil

Wednesday, July 2

Sorry

Just went through my video library and Chuck is the only one I want to watch. Can't find anything that I want to watch.

I don't know how but It is making me do what It wants. Now I want to see Chuck.

The reason that now I have is that one episode won't end Chuck.

So here it goes and I am sorry, I involved you guys. But I blog so that you know everything.

Love you. Helpless.

([^_^])

Just ordered a Pizza

Wanted to see if I CAN see Chuck. But I found out that Chuck season two won't be aired till the end of September.

So yes I am afraid of living without any new episodes of Chuck that I CAN/WILL see because of which I can't end season one.

Am I wrong?

Why do you want to know the answer to that question?

([^_^])

Am I committing a sin?

No, not really even though It wants me to watch but I just can't find enough heart to finish Chuck season one. Can't force myself where I don't have to and there are other EASIER options. Just like vGames. So I will pray to the Almighty that I don't want to go against It's will but I also don't have the courage(since technically I am not sinning, I don't have enough motivation) to fulfill It's wish. So I will wait till the Almighty gives me the strength to finish S1.

([^_^])

Had a peach

Ok! I was tired to trying to find the right game and thought I should force myself to play N4S or MGS.

Just tried to play them and wrong.

A mistake.

Now that I had enough time to think, the problem was not that I was tired but that I was afraid of what I would do when I don't want to play Secret Agent Clank or Go Sudoku. Loneliness and I couldn't talk to any one about it. So it took me time to figure out what the problem was.

But now I have been thinking about it for almost two days and I think three Commando games, two Tomb Raider games on my PC and Secret Agent Clank and Go Sudoku on PSP are enough to keep me busy till the new Tomb Raider comes out.

And I trust Allah that it will keep me busy till I get the chance to learn from myBooboo and from my friends how to play N4S or MGS, is there is a need.

Will watch a movie now.

([^_^])

Booba, I want to make you happy too :)

Have been messing with games and the game controller.

Sorry about the delay but had to check what was it that I was doing wrong.

Well installed the game Commando 2: Men of Courage which I bought today. Yesterday I was unable to install it from the CDs that I had. But unfortunately, it does not supports game controller.

So I can either move to next Commando game or get MGS for PC. The thing is that I love Commando game and I don't want to miss any of it's versions. I have played and finished 2 and now want to play the next one. So just for the sake of the controller, I don't think I should get a new game. So I will wait to test the controller later.

Got N4S Pro Street and it works great on PSP. The difference between PSP and PC is the portability of PSP, love that. So I need a game beside Secret Agent Clank to play when I am tired of SAC and its choice to only save the game around levels and not within the levels.

Was going to find how to play N4S PS and downloaded this video and just saw it, man what in the name of God is he talking about. I felt like ... I don't even know how to explain what I felt like. All I know is that I had that semi amazed, semi shocked smile on my face and I was motionless. Saw it half way and closed it. Knew, I have to learn a lot. Trail and Error seems to be the only way.

Booboo, my Sweetest FEMALE Goddess of polite beauty Booboo, are you that good with any of the games?

[scared] :-O

Feel hungry, very hungry now and would eat something.

...

Just had a discussion with my little sister(nothing cooked at home and thought lets see if she would let me enjoy a meal with her and let me be pleased that I made her happy) and now I am being forced to order Pizza Huts Appetizer Box-Trio, she loves that and I love her. So in the name of Unity(family first), I will eat chocolate later and will FORCE myself to enjoy PHABT. Yes not NO to Chocolate but later.

([^_^])

V the warrior

Against theRealDouchBag

([^_^])

Love you girl. You are the one. Very proud of you. And you too. Very.

Thanks.

Love you.

Now listening to Din Mein Teri Yaad by Fakhir

Going to the market for N4S and stablizer fuses

Can't wait.

TIH, definitely tomorrow.

;)

([^_^])

Now listening to Yeh Maza Zindagi Ka by Junoon