home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC Underground
/
UNDERGROUND.ISO
/
share
/
a86
/
a06a.doc
< prev
next >
Wrap
Text File
|
1994-01-23
|
17KB
|
350 lines
CHAPTER 6 THE 86 INSTRUCTION SET
Effective Addresses
Most memory data accessing in the 86 family is accomplished via
the mechanism of the effective address. Wherever an effective
address specifier "eb", "ew" or "ed" appears in the list of 8086
instructions, you may use a wide variety of actual operands in
that instruction. These include general registers, memory
variables, and a variety of indexed memory quantities.
GENERAL REGISTERS: Wherever an "ew" appears, you can use any of
the 16-bit registers AX,BX,CX,DX,SI,DI,SP, or BP. Wherever an
"eb" appears, you can use any of the 8-bit registers
AL,BL,CL,DL,AH,BH,CH, or DH. For example, the "ADD ew,rw" form
subsumes the 16-bit register-to-register adds; for example, ADD
AX,BX; ADD SI,BP; ADD SP,AX.
MEMORY VARIABLES: Wherever an "eb", "ew", or "ed" appears, you
can use a memory variable of the indicated size: byte, word, or
doubleword. Variables are typically declared in the DATA
segment, using a DW declaration for a word variable, or a DB
declaration for a byte variable. For example, you can declare
variables:
DATA_PTR DW ?
ESC_CHAR DB ?
Later, you can load or store these variables:
MOV ESC_CHAR,BL ; store the byte variable ESC_CHAR
MOV DATA_PTR,081 ; initialize DATA_PTR
MOV SI,DATA_PTR ; load DATA_PTR into SI for use
LODSW ; fetch the word pointed to by DATA_PTR
Alternatively, you can address specific unnamed memory locations
by enclosing the location value in square brackets; for example,
MOV AL,[02000] ; load contents of location 02000 into AL
Note that A86 discerned from context (loading into AL) that a
BYTE at 02000 was intended. Sometimes this is impossible, and
you must specify byte or word:
INC B[02000] ; increment the byte at location 02000
MOV W[02000],0 ; set the WORD at location 02000 to zero
6-2
INDEXED MEMORY: The 86 supports the use of certain registers as
base pointers and index registers into memory. BX and BP are the
base registers; SI and DI are the index registers. You may
combine at most one base register, at most one index register,
and a constant number into a run time pointer that determines the
location of the effective address memory to be used in the
instruction. These can be given explicitly, by enclosing the
index registers in brackets:
MOV AX,[BX]
MOV CX,W[SI+17]
MOV AX,[BX+SI+5]
MOV AX,[BX][SI]5 ; another way to write the same instr.
Or, indexing can be accomplished by declaring variables in a
based structure (see the STRUC directive in Chapter 9):
STRUC [BP] ; NOTE: based structures are unique to A86!
BP_SAVE DW ? ; BP_SAVE is a word at [BP]
RET_ADDR DW ? ; RET_ADDR is a word at [BP+2]
PARM1 DW ? ; PARM1 is a word at [BP+4]
PARM2 DW ? ; PARM2 is a word at [BP+6]
ENDS ; end of structure
INC PARM1 ; equivalent to INC W[BP+4]
Finally, indexing can be done by mixing explicit components with
declared ones:
TABLE DB 4,2,1,3,5
MOV AL,TABLE[BX] ; load byte number BX of TABLE
Segmentation and Effective Addresses
The 86 family has four segment registers, CS, DS, ES, and SS,
used to address memory. Each segment register points to 64K
bytes of memory within the 1-megabyte memory space of the 86.
(The start of the 64K is calculated by multiplying the segment
register value by 16; i.e., by shifting the value left by one hex
digit.) If your program's code, data and stack areas can all fit
in the same 64K bytes, you can leave all the segment registers
set to the same value. In that case, you won't have to think
about segment registers: no matter which one is used to address
memory, you'll still get the same 64K. If your program needs
more than 64K, you must point one or more segment registers to
other parts of the memory space. In this case, you must take
care that your memory references use the segment registers you
intended.
Each effective address memory access has a default segment
register, to be used if you do not explicitly specify which
segment register you wish. For most effective addresses, the
default segment register is DS. The exceptions are those
effective addresses that use the BP register for indexing. All
BP-indexed memory references have a default of SS. (This is
because BP is intended to be used for addressing local variables,
stored on the stack.)
6-3
If you wish your memory access to use a different segment
register, you provide a segment override byte before the
instruction containing the effective address operand. In the A86
language, you code the override by giving the name of the segment
register you wish before the instruction mnemonic. For example,
suppose you want to load the AL register with the memory byte
pointed to by BX. If you code MOV AL,[BX], the DS register will
be used to determine which 64K segment BX is pointing to. If you
want the byte to come from the CS-segment instead, you code CS
MOV AL,[BX]. Be aware that the segment override byte has effect
only upon the single instruction that follows it. If you have a
sequence of instructions requiring overrides, you must give an
override byte before every instruction in the sequence. (In that
case, you may wish to consider changing the value of the default
segment register for the duration of the sequence.)
NOTE: This method for providing segment overrides is unique to
the A86 assembler! The assemblers provided by Intel and IBM
(MS-DOS) attempt to figure out segment allocation for you, and
plug in segment override bytes "behind your back". In order to
do this, those assemblers require you to inform them which
variables and structures are pointed to by which segment
registers. That is what the ASSUME directive in those assemblers
is all about. I wrote Intel's first 86 assembler, ASM86, so I
have been watching the situation since day one. Over the years,
I have concluded that the ASSUME mechanism creates far, far more
confusion that it solves. So I scrapped it; and the result is an
assembler with far less red tape. But if your program needs more
than 64K, you do have to manage those segment registers yourself;
so take care!
Effective Use of Effective Addresses
Remember that all of the common instructions of the 86 family
allow effective addresses as operands. (The only major functions
that don't are the AL/AX specific ones: multiply, divide, and
input/output). This means that you don't have to funnel many
numbers through AL or AX just to do something with them. You can
perform all the common arithmetic, PUSH/POP, and MOVes from any
general register to any general register; from any memory
location (indexed if you like) to any register; and (this is most
often overlooked) from any register TO memory. The only thing
you can't do in general is memory-to-memory. Among the more
common operations that inexperienced 86 programmers overlook are:
* setting memory variables to immediate values
* testing memory variables, and comparing them to constants
* preserving memory variables by PUSHing and POPping them
* incrementing and decrementing memory variables
* adding into memory variables
6-4
Encoding of Effective Addresses
Unless you are concerned with the nitty-gritty details of 86
instruction encoding, you don't need to read this section.
Every instruction with an effective address has an encoded byte,
known as the effective address byte, following the 1-byte opcode
for the instruction. (For obscure reasons, Intel calls this byte
the ModRM byte.) If the effective address is a memory variable,
or an indexed memory location with a non-zero constant offset,
then the effective address byte will be immediately followed by
the offset amount. Amounts in the range -128 to +127 are given
by a single signed byte, denoted by "d8" in the table below.
Amounts requiring a 2-byte representation are denoted by "d16" in
the table below. As with all 16-bit memory quantities in the 86
family, the word is stored with the least significant byte FIRST.
The following table of effective address byte values is organized
into 32 rows and 8 columns. The 32 rows give the possible values
for the effective address operand: 8 registers and 24 memory
indexing modes. A 25th indexing mode, [BP] with zero
displacement, has been pre-empted by the simple-memory-variable
case. If you code [BP] with no displacement, you will get
[BP]+d8, with a d8-value of zero.
The 8 columns of the table reflect further information given by
the effective address byte. Usually, this is the identity of the
other (always a register) operand of a 2-operand instruction.
Those instructions are identified by a "/r" following the opcode
byte in the instruction list. Sometimes, the information given
supplements the opcode byte in identifying the instruction
itself. Those instructions are identified by a "/" followed by a
digit from 0 through 7. The digit tells which of the 8 columns
you should use to find the effective address byte.
For example, suppose you have a perverse wish to know the precise
bytes encoded by the instruction SUB B[BX+17],100. This
instruction subtracts an immediate quantity, 100, from an
effective address quantity, B[BX+17]. By consulting the
instruction list, you find the general form SUB eb,ib. The
opcode bytes given there are 80 /5 ib. The "/5" denotes an
effective address byte, whose value will be taken from column 5
of the following table. The offset 17 decimal, which is 11 hex,
will fit in a single "d8" byte, so we take our value from the
"[BX] + d8" row. The table tells us that the effective address
byte is 6F. Immediately following the 6F is the offset, 11 hex.
Following that is the ib-value of 100 decimal, which is 64 hex.
So the bytes generated by SUB B[BX+17],100 are 80 6F 11 64.
6-5
Table of Effective Address byte values
s = ES CS SS DS
rb = AL CL DL BL AH CH DH BH
rw = AX CX DX BX SP BP SI DI
digit= 0 1 2 3 4 5 6 7
Effective
EA byte address:
values: 00 08 10 18 20 28 30 38 [BX + SI]
01 09 11 19 21 29 31 39 [BX + DI]
02 0A 12 1A 22 2A 32 3A [BP + SI]
03 0B 13 1B 23 2B 33 3B [BP + DI]
04 0C 14 1C 24 2C 34 3C [SI]
05 0D 15 1D 25 2D 35 3D [DI]
06 0E 16 1E 26 2E 36 3E d16 (simple var)
07 0F 17 1F 27 2F 37 3F [BX]
40 48 50 58 60 68 70 78 [BX + SI] + d8
41 49 51 59 61 69 71 79 [BX + DI] + d8
42 4A 52 5A 62 6A 72 7A [BP + SI] + d8
43 4B 53 5B 63 6B 73 7B [BP + DI] + d8
44 4C 54 5C 64 6C 74 7C [SI] + d8
45 4D 55 5D 65 6D 75 7D [DI] + d8
46 4E 56 5E 66 6E 76 7E [BP] + d8
47 4F 57 5F 67 6F 77 7F [BX] + d8
80 88 90 98 A0 A8 B0 B8 [BX + SI] + d16
81 89 91 99 A1 A9 B1 B9 [BX + DI] + d16
82 8A 92 9A A2 AA B2 BA [BP + SI] + d16
83 8B 93 9B A3 AB B3 BB [BP + DI] + d16
84 8C 94 9C A4 AC B4 BC [SI] + d16
85 8D 95 9D A5 AD B5 BD [DI] + d16
86 8E 96 9E A6 AE B6 BE [BP] + d16
87 8F 97 9F A7 AF B7 BF [BX] + d16
C0 C8 D0 D8 E0 E8 F0 F8 ew=AX eb=AL
C1 C9 D1 D9 E1 E9 F1 F9 ew=CX eb=CL
C2 CA D2 DA E2 EA F2 FA ew=DX eb=DL
C3 CB D3 DB E3 EB F3 FB ew=BX eb=BL
C4 CC D4 DC E4 EC F4 FC ew=SP eb=AH
C5 CD D5 DD E5 ED F5 FD ew=BP eb=CH
C6 CE D6 DE E6 EE F6 FE ew=SI eb=DH
C7 CF D7 DF E7 EF F7 FF ew=DI eb=BH
d8 denotes an 8-bit displacement following the EA byte, to be
sign-extended and added to the index.
d16 denotes a 16-bit displacement following the EA byte, to be
added to the index.
Default segment register is SS for effective addresses containing
a BP index; DS for other memory effective addresses.
6-6
How to Read the Instruction Set Chart
The following chart summarizes the machine instructions you can
program with A86. In order to use the chart, you need to learn
the meanings of the specifiers (each given by 2 lower case
letters) that follow most of the instruction mnemonics. Each
specifier indicates the type of operand (register byte, immediate
word, etc.) that follows the mnemonic to produce the given
opcodes.
"c" means the operand is a code label, pointing to a part of the
program to be jumped to or called. A86 will also accept a
constant offset in this place (or a constant segment-offset
pair in the case of "cd"). "cb" is a label within about 128
bytes (in either direction) of the current location. "cw" is
a label within the same code segment as this program; "cd" is
a pair of constants separated by a colon-- the segment value
to the left of the colon, and the offset to the right. Note
that in both the cb and cw cases, the object code generated
is the offset from the location following the current
instruction, not the absolute location of the label operand.
In some assemblers (most notably for the Z-80 processor) you
have to code this offset explicitly by putting "$-" before
every relative jump operand in your source code. You do NOT
need to, and should not do so with A86.
"e" means the operand is an Effective Address. The concept of
an Effective Address is central to the 86 machine
architecture, and thus to 86 assembly language programming.
It is described in detail at the start of this chapter. We
summarize here by saying that an Effective Address is either
a general purpose register, a memory variable, or an indexed
memory quantity. For example, the instruction "ADD rb,eb"
includes the instructions: ADD AL,BL, and ADD CH,BYTEVAR, and
ADD DL,B[BX+17].
"i" means the operand is an immediate constant, provided as part
of the instruction itself. "ib" is a byte-sized constant;
"iw" is a constant occupying a full 16-bit word. The operand
can also be a label, defined with a colon. In that case, the
immediate constant which is the location of the label is
used. Examples: "MOV rw,iw" includes the instructions: MOV
AX,17, or MOV SI,VAR_ARRAY, where "VAR_ARRAY:" appears
somewhere in the program, defined with a colon. NOTE that if
VAR_ARRAY were defined without a colon, e.g., "VAR_ARRAY DW
1,2,3", then "MOV SI,VAR_ARRAY" would be a "MOV rw,ew" NOT a
"MOV rw,iw". The MOV would move the contents of memory at
VAR_ARRAY (in this case 1) into SI, instead of the location
of the memory. To load the location, you can code "MOV
SI,OFFSET VAR_ARRAY".
6-7
"m" means a memory variable or an indexed memory quantity; i.e.,
any Effective Address EXCEPT a register.
"r" means the operand is a general purpose register. The 8 "rb"
registers are AL,BL,CL,DL,AH,BH,CH,DH; the 8 "rw" registers
are AX,BX,CX,DX,SI,DI,BP,SP.
NOTE: Instruction forms marked with "*" by the mnemonic are part
of the extended 186/286/NEC instruction set. Instructions marked
with "#" are unique to the NEC processors. These instructions
will NOT work on the 8088 of the IBM-PC; nor will they work on
the 8086; nor will the NEC instructions work on the 186/286/386
etc. If you wish your programs to run on all PC's, you should
assemble under the +P0 switch, which will cause A86 to outlaw all
the extra instruction forms.