An effective address is any operand to an instruction which references memory. Effective addresses, in NASM, have a very simple syntax: they consist of an expression evaluating to the desired address, enclosed in square brackets. For example:
wordvar dw 123
mov ax,[wordvar]
mov ax,[wordvar+1]
mov ax,[es:wordvar+bx]
Anything not conforming to this simple system is not a valid memory reference in NASM,
for example es:wordvar[bx].
More complicated effective addresses, such as those involving more than one register, work in exactly the same way:
mov eax,[ebx*2+ecx+offset]
mov ax,[bp+di+8]
NASM is capable of doing algebra on these effective addresses, so that things which don’t necessarily look legal are perfectly all right:
mov eax,[ebx*5] ; assembles as [ebx*4+ebx]
mov eax,[label1*2-label2] ; ie [label1+(label1-label2)]
Some forms of effective address have more than one assembled form; in most such cases
NASM will generate the smallest form it can. For example, there are distinct assembled
forms for the 32-bit effective addresses [eax*2+0] and [eax+eax], and NASM will generally generate the latter on the
grounds that the former requires four bytes to store a zero offset.
NASM has a hinting mechanism which will cause [eax+ebx]
and [ebx+eax] to generate different opcodes; this is
occasionally useful because [esi+ebp] and [ebp+esi] have different default segment registers.
However, you can force NASM to generate an effective address in a particular form by
the use of the keywords BYTE, WORD, DWORD and NOSPLIT. If you
need [eax+3] to be assembled using a double-word offset
field instead of the one byte NASM will normally generate, you can code [dword eax+3]. Similarly, you can force NASM to use a byte offset
for a small value which it hasn’t seen on the first pass (see Раздел 2.8 for an
example of such a code fragment) by using [byte eax+offset].
As special cases, [byte eax] will code [eax+0] with a byte offset of zero, and [dword eax] will code it with a double-word offset of zero. The
normal form, [eax], will be coded with no offset field.
The form described in the previous paragraph is also useful if you are trying to access data in a 32-bit segment from within 16 bit code. In particular, if you need to access data with a known offset that is larger than will fit in a 16-bit value, if you don’t specify that it is a dword offset, NASM will cause the high word of the offset to be lost.
Similarly, NASM will split [eax*2] into [eax+eax] because that allows the offset field to be absent and
space to be saved; in fact, it will also split [eax*2+offset] into [eax+eax+offset].
You can combat this behaviour by the use of the NOSPLIT
keyword: [nosplit eax*2] will force [eax*2+0] to be generated literally.
In BITS 64 mode, displacements, for the most part, remain
32 bits and are sign extended prior to use. The exception is one restricted form of the
mov instruction: between an AL, AX, EAX, or RAX register and a 64-bit absolute address (no registers are
allowed in the effective address, and the address cannot be RIP-relative). In NASM
syntax, use of the 64-bit absolute form requires QWORD.
Examples in NASM syntax:
mov eax, [1] ; 32 bit, with sign extension
mov al, [rax-1] ; 32 bit, with sign extension
mov al, [qword 0x1122334455667788] ; 64-bit absolute
mov al, [0x1122334455667788] ; truncated to 32-bit (warning)
In 64-bit mode, a new form of effective addressing is available to make it easier to
write position-independent code. Any memory reference may be made RIP relative (RIP is the instruction pointer register, which contains the
address of the location immediately following the current instruction).
In NASM syntax, there are two ways to specify RIP-relative addressing:
mov dword [rip+10], 1
stores the value 1 ten bytes after the end of the instruction. 10 can also be a symbolic constant, and will be treated the same
way. On the other hand,
mov dword [symb wrt rip], 1
stores the value 1 into the address of symbol symb. This
is distinctly different than the behavior of:
mov dword [symb+rip], 1
which takes the address of the end of the instruction, adds the address of symb to it, then stores the value 1 there. If symb is a variable, this will not store the value 1 into the symb variable!
Yasm also supports the following syntax for RIP-relative addressing. The REL keyword makes it produce RIP-relative addresses, while the ABS keyword makes
it produce non-RIP-relative addresses:
mov [rel sym], rax ; RIP-relative
mov [abs sym], rax ; not RIP-relative
The behavior of mov [sym], rax depends on a mode set by
the DEFAULT directive (see Раздел 4.2), as follows.
The default mode at Yasm start-up is always ABS, and in
REL mode, use of registers, a FS or GS segment override, or an
explicit ABS override will result in a non-RIP-relative
effective address.
default rel
mov [sym], rbx ; RIP-relative
mov [abs sym], rbx ; not RIP-relative (explicit override)
mov [rbx+1], rbx ; not RIP-relative (register use)
mov [fs:sym], rbx ; not RIP-relative (fs or gs use)
mov [ds:sym], rbx ; RIP-relative (segment, but not fs or gs)
mov [rel sym], rbx ; RIP-relative (redundant override)
default abs
mov [sym], rbx ; not RIP-relative
mov [abs sym], rbx ; not RIP-relative
mov [rbx+1], rbx ; not RIP-relative
mov [fs:sym], rbx ; not RIP-relative
mov [ds:sym], rbx ; not RIP-relative
mov [rel sym], rbx ; RIP-relative (explicit override)