INTRODUCTION
The program control instructions direct the flow of a program and allow the flow to change. A change in flow often occurs after a decision made with the CMP or TEST instruction is followed by a conditional jump instruction. This chapter explains the program control instructions, including the jumps, calls, returns, interrupts, and machine control instructions.
This chapter also presents the relational assembly language statements (.IF, .ELSE, .ELSEIF, .ENDIF, .WHILE, .ENDW, .REPEAT, and .UNTIL) that are available in version 6.xx and above of MASM or TASM, with version 5.xx set for MASM compatibility. These relational assembly language commands allow the programmer to develop control flow portions of the program with C/C++ language efficiency.
CHAPTER OBJECTIVES
Upon completion of this chapter, you will be able to:
1. Use both conditional and unconditional jump instructions to control the flow of a program.
2. Use the relational assembly language statements .IF, .REPEAT, .WHILE, and so forth in programs.
3. Use the call and return instructions to include procedures in the program structure.
4. Explain the operation of the interrupts and interrupt control instructions.
5. Use machine control instructions to modify the flag bits.
6. Use ENTER and LEAVE to enter and leave programming structures.
THE JUMP GROUP
The main program control instruction, jump (JMP), allows the programmer to skip sections of a program and branch to any part of the memory for the next instruction. A conditional jump instruction allows the programmer to make decisions based upon numerical tests. The results of numerical tests are held in the flag bits, which are then tested by conditional jump instructions. Another instruction similar to the conditional jump, the conditional set, is explained with the conditional jump instructions in this section.
In this section of the text, all jump instructions are illustrated with their uses in sample pro- grams. Also revisited are the LOOP and conditional LOOP instructions, first presented in Chapter 3, because they are also forms of the jump instruction.
Unconditional Jump (JMP)
Three types of unconditional jump instructions (see Figure 6–1) are available to the micro- processor: short jump, near jump, and far jump. The short jump is a 2-byte instruction that allows jumps or branches to memory locations within +127 and –128 bytes from the address following the jump. The 3-byte near jump allows a branch or jump within ±32K bytes (or any- where in the current code segment) from the instruction in the current code segment. Remember that segments are cyclic in nature, which means that one location above offset address FFFFH is offset address 0000H. For this reason, if you jump 2 bytes ahead in memory and the instruction pointer addresses offset address FFFFH, the flow continues at offset address 0001H. Thus, a dis- placement of ±32K bytes allows a jump to any location within the current code segment. Finally, the 5-byte far jump allows a jump to any memory location within the real memory system. The short and near jumps are often called intrasegment jumps, and the far jumps are often called intersegment jumps.
In the 80386 through the Core2 processors, the near jump is within ±2G if the machine is operated in the protected mode, with a code segment that is 4G bytes long. If operated in the real mode, the near jump is within ±32K bytes. In the protected mode, the 80386 and above use a 32- bit displacement that is not shown in Figure 6–1. If the Pentium 4 is operated in the 64-bit mode, a jump can be to any address in its 1T memory space.
Short Jump. Short jumps are called relative jumps because they can be moved, along with their related software, to any location in the current code segment without a change. This is because the jump address is not stored with the opcode. Instead of a jump address, a distance, or displacement, follows the opcode. The short jump displacement is a distance represented by a 1-byte signed number whose value ranges between +127 and -128. The short jump instruction appears in Figure 6–2. When the microprocessor executes a short jump, the displacement is sign- extended and added to the instruction pointer (IP/EIP) to generate the jump address within the current code segment. The short jump instruction branches to this new address for the next instruction in the program.
Example 6–1 shows how short jump instructions pass control from one part of the program to another. It also illustrates the use of a label (a symbolic name for a memory address) with the jump instruction. Notice how one jump (JMP SHORT NEXT) uses the SHORT directive to force
a short jump, while the other does not. Most assembler programs choose the best form of the jump instruction so the second jump instruction (JMP START) also assembles as a short jump. If the address of the next instruction (0009H) is added to the sign-extended displacement (0017H) of the first jump, the address of NEXT is at location 0017H + 0009H or 0020H.
Whenever a jump instruction references an address, a label normally identifies the address. The JMP NEXT instruction is an example; it jumps to label NEXT for the next instruction. It is very rare to use an actual hexadecimal address with any jump instruction, but the assembler sup- ports addressing in relation to the instruction pointer by using the $+a displacement. For exam- ple, the JMP $+2 instruction jumps over the next two memory locations (bytes) following the JMP instruction. The label NEXT must be followed by a colon (NEXT:) to allow an instruction to reference it for a jump. If a colon does not follow a label, you cannot jump to it. Note that the only time a colon is used after a label is when the label is used with a jump or call instruction. This is also true in Visual C++.
Near Jump. The near jump is similar to the short jump, except that the distance is farther. A near jump passes control to an instruction in the current code segment located within ±32K bytes from the near jump instruction. The distance is ±2G in the 80386 and above when operated in protected mode. The near jump is a 3-byte instruction that contains an opcode followed by a signed 16-bit displacement. In the 80386 through the Pentium 4 processors, the displacement is 32 bits and the near jump is 5 bytes long. The signed displacement adds to the instruction pointer (IP) to generate the jump address. Because the signed displacement is in the range of ±32K, a
near jump can jump to any memory location within the current real mode code segment. The protected mode code segment in the 80386 and above can be 4G bytes long, so the 32-bit displacement allows a near jump to any location within ±2G bytes. Figure 6–3 illustrates the operation of the real mode near jump instruction.
The near jump is also relocatable (as was the short jump) because it is also a relative jump. If the code segment moves to a new location in the memory, the distance between the jump instruction and the operand address remains the same. This allows a code segment to be relocated by simply moving it. This feature, along with the relocatable data segments, makes the Intel family of microprocessors ideal for use in a general-purpose computer system. Software can be written and loaded anywhere in the memory and function without modification because of the relative jumps and relocatable data segments.
Example 6–2 shows the same basic program that appeared in Example 6–1, except that the jump distance is greater. The first jump (JMP NEXT) passes control to the instruction at offset memory location 0200H within the code segment. Notice that the instruction assembles as E9 0200 R. The letter R denotes a relocatable jump address of 0200H. The relocatable address of 0200H is for the assembler program’s internal use only. The actual machine language instruction assembles as E9 F6 01, which does not appear in the assembler listing. The actual displacement is 01F6H for this jump instruction. The assembler lists the jump address as 0200 R, so the address is easier to interpret as software is developed. If the linked execution file (.EXE) or command file (.COM) is displayed in hexadecimal code, the jump instruction appears as E9 F6 01.
Far Jump. A far jump instruction (see Figure 6–4) obtains a new segment and offset address to accomplish the jump. Bytes 2 and 3 of this 5-byte instruction contain the new offset address; bytes
4 and 5 contain the new segment address. If the microprocessor (80286 through the Core2) is operated in the protected mode, the segment address accesses a descriptor that contains the base address of the far jump segment. The offset address, which is either 16 or 32 bits, contains the off- set address within the new code segment.
Example 6–3 lists a short program that uses a far jump instruction. The far jump instruction sometimes appears with the FAR PTR directive, as illustrated. Another way to obtain a far jump is to define a label as a far label. A label is far only if it is external to the current code segment or procedure. The JMP UP instruction in the example references a far label. The label UP is defined as a far label by the EXTRN UP:FAR directive. External labels appear in pro- grams that contain more than one program file. Another way of defining a label as global is to use a double colon (LABEL::) following the label in place of the single colon. This is required inside procedure blocks that are defined as near if the label is accessed from outside the procedure block.
When the program files are joined, the linker inserts the address for the UP label into the JMP UP instruction. It also inserts the segment address in the JMP START instruction. The segment address in JMP FAR PTR START is listed as – – – – R for relocatable; the segment address in JMP UP is listed as – – – – E for external. In both cases, the – – – – is filled in by the linker when it links or joins the program files.
Jumps with Register Operands. The jump instruction can also use a 16- or 32-bit register as an operand. This automatically sets up the instruction as an indirect jump. The address of the jump is in the register specified by the jump instruction. Unlike the displacement associated
with the near jump, the contents of the register are transferred directly into the instruction pointer. An indirect jump does not add to the instruction pointer, as with short and near jumps. The JMP AX instruction, for example, copies the contents of the AX register into the IP when the jump occurs. This allows a jump to any location within the current code segment. In the 80386 and above, a JMP EAX instruction also jumps to any location within the current code segment; the difference is that in protected mode the code segment can be 4G bytes long, so a 32-bit offset address is needed.
Example 6–4 shows how the JMP AX instruction accesses a jump table in the code segment. This DOS program reads a key from the keyboard and then modifies the ASCII code to 00H in AL for a ‘1’, 01H for a ‘2’, and 02H for a ‘3’. If a ‘1’, ‘2’, or ‘3’ is typed, AH is cleared to 00H. Because the jump table contains 16-bit offset addresses, the contents of AX are doubled to 0, 2, or 4, so a 16-bit entry in the table can be accessed. Next, the offset address of the start of the jump table is loaded to SI, and AX is added to form the reference to the jump address. The MOV AX,[SI] instruction then fetches an address from the jump table, so the JMP AX instruction jumps to the addresses (ONE, TWO, or THREE) stored in the jump table.
Indirect Jumps Using an Index. The jump instruction may also use the [ ] form of addressing to directly access the jump table. The jump table can contain offset addresses for near indirect jumps, or segment and offset addresses for far indirect jumps. (This type of jump is also known as a double-indirect jump if the register jump is called an indirect jump.) The assembler assumes that the jump is near unless the FAR PTR directive indicates a far jump instruction. Here Example 6–5 repeats Example 6–4 by using the JMP TABLE [SI] instead of JMP AX. This reduces the length of the program.
The mechanism used to access the jump table is identical with a normal memory reference. The JMP TABLE [SI] instruction points to a jump address stored at the code segment offset loca- tion addressed by SI. It jumps to the address stored in the memory at this location. Both the register and indirect indexed jump instructions usually address a 16-bit offset. This means that both types of jumps are near jumps. If a JMP FAR PTR [SI] or JMP TABLE [SI], with TABLE data defined with the DD directive appears in a program, the microprocessor assumes that the jump table contains doubleword, 32-bit addresses (IP and CS).
Conditional Jumps and Conditional Sets
Conditional jump instructions are always short jumps in the 8086 through the 80286 micro- processors. This limits the range of the jump to within +127 bytes and -128 bytes from the location following the conditional jump. In the 80386 and above, conditional jumps are either short or near jumps (±32K). In the 64-bit mode of the Pentium 4, the near jump distance is ±2G for the conditional jumps. This allows these microprocessors to use a conditional jump to any location within the current code segment. Table 6–1 lists all the conditional jump instructions with their test conditions. Note that the Microsoft MASM version 6.x assembler automatically adjusts conditional jumps if the distance is too great.
The conditional jump instructions test the following flag bits: sign (S), zero (Z), carry (C), parity (P), and overflow (0). If the condition under test is true, a branch to the label associated with the jump instruction occurs. If the condition is false, the next sequential step in the program executes. For example, a JC will jump if the carry bit is set.
The operation of most conditional jump instructions is straightforward because they often test just one flag bit, although some test more than one. Relative magnitude comparisons require more complicated conditional jump instructions that test more than one flag bit.
numbers. The 16- and 32-bit numbers follow the same order as the 8-bit numbers, except that they are larger. Notice that an FFH (255) is above the 00H in the set of unsigned numbers, but an FFH (-1) is less than 00H for signed numbers. Therefore, an unsigned FFH is above 00H, but a signed FFH is less than 00H.
When signed numbers are compared, use the JG, JL, JGE, JLE, JE, and JNE instructions. The terms greater than and less than refer to signed numbers. When unsigned numbers are com- pared, use the JA, JB, JAB, JBE, JE, and JNE instructions. The terms above and below refer to unsigned numbers.
The remaining conditional jumps test individual flag bits, such as overflow and parity. Notice that JE has an alternative opcode JZ. All instructions have alternates, but many aren’t used in programming because they don’t usually fit the condition under test. (The alternates appear in Appendix B with the instruction set listing.) For example, the JA instruction (jump if above) has the alternative JNBE (jump if not below or equal). A JA functions exactly as a JNBE, but a JNBE is awkward in many cases when compared to a JA.
The conditional jump instructions all test flag bits except for JCXZ (jump if CX = 0) and JECXZ (jump if ECX = 0). Instead of testing flag bits, JCXZ directly tests the contents of the CX register without affecting the flag bits, and JECXZ tests the contents of the ECX register. For the JCXZ instruction, if CX = 0, a jump occurs, and if CX != 0, no jump occurs. Likewise for the JECXZ instruction, if ECX = 0, a jump occurs; if ECX != 0, no jump occurs. In the Pentium 4 or Core2 operated in the 64-bit mode, the JRCXZ instruction jumps is RCX = 0.
A program that uses JCXZ appears in Example 6–6. Here, the SCASB instruction searches a table for 0AH. Following the search, a JCXZ instruction tests CX to see if the count has reached zero. If the count is zero, the 0AH is not found in the table. The carry flag is used in this example to pass the not found condition back to the calling program. Another method used to test to see if the data are found is the JNE instruction. If JNE replaces JCXZ, it performs the same function. After the SCASB instruction executes, the flags indicate a not-equal condition if the data were not found in the table.
The Conditional Set Instructions. In addition to the conditional jump instructions, the 80386 through the Core2 processors also contain conditional set instructions. The conditions tested by conditional jumps are put to work with the conditional set instructions. The conditional set instructions set a byte to either 01H or clear a byte to 00H, depending on the out- come of the condition under test. Table 6–2 lists the available forms of the conditional set instructions.
These instructions are useful where a condition must be tested at a point much later in the program. For example, a byte can be set to indicate that the carry is cleared at some point in the program by using the SETNC MEM instruction. This instruction places 01H into memory location MEM if carry is cleared, and 00H into MEM if carry is set. The contents of MEM can be
tested at a later point in the program to determine if carry is cleared at the point where the SETNC MEM instruction executed.
LOOP
The LOOP instruction is a combination of a decrement CX and the JNZ conditional jump. In the 8086 through the 80286 processors, LOOP decrements CX; if CX != 0, it jumps to the address indicated by the label. If CX becomes 0, the next sequential instruction executes. In the 80386 and above, LOOP decrements either CX or ECX, depending upon the instruction mode. If the 80386 and above operate in the l6-bit instruction mode, LOOP uses CX; if operated in the 32-bit instruction mode, LOOP uses ECX. This default is changed by the LOOPW (using CX) and LOOPD (using ECX) instructions in the 80386 through the Core2. In the 64-bit mode, the loop counter is in RCX and is 64 bits wide.
Example 6–7 shows how data in one block of memory (BLOCK1) add to data in a second block of memory (BLOCK2), using LOOP to control how many numbers add. The LODSW and STOSW instructions access the data in BLOCK1 and BLOCK2. The ADD AX, ES:[DI] instruction accesses the data in BLOCK2 located in the extra segment. The only reason that BLOCK2 is in the extra segment is that DI addresses extra segment data for the STOSW instruction. The .STARTUP directive only loads DS with the address of the data segment. In this example, the extra segment also addresses data in the data segment, so the con- tents of DS are copied to ES through the accumulator. Unfortunately, there is no direct move from segment register to segment register instruction.
Conditional LOOPs. As with REP, the LOOP instruction also has conditional forms: LOOPE and LOOPNE. The LOOPE (loop while equal) instruction jumps if CX != 0 while an equal condition exists. It will exit the loop if the condition is not equal or if the CX register decrements to 0. The LOOPNE (loop while not equal) instruction jumps if CX != 0 while a not-equal condition exists. It will exit the loop if the condition is equal or if the CX register decrements to 0. In the 80386 through the Core2 processors, the conditional LOOP instruction can use either CX or ECX as the counter. The LOOPEW/LOOPED or LOOPNEW/LOOPNED instructions override the instruction mode if needed. Under 64-bit operation, the loop counter uses RCX and is 64 bits in width.
As with the conditional repeat instructions, alternates exist for LOOPE and LOOPNE. The LOOPE instruction is the same as LOOPZ, and the LOOPNE instruction is the same as LOOPNZ. In most programs, only the LOOPE and LOOPNE apply.