I'm trying to have a whole picture of all the possible addressing modes of X86 instructions. Starting from this I studied the Intel IA-32 reference and multiple secondary references found online.

I'd like to understand them correctly, so here's my doubts:

16bit Addressing Modes

  • mod == 0b11: direct value contained in register is accessed, quite clear
  • mod != 0b11: these are all indirect values, with optional 8 bit or 16 bit displacement to the final value, so we refer to the value contained in the computed address.

My doubts:

  • the 16 bit displacement is signed or unsigned? Eg mov ax, [SI + 40000] vs mov ax, [SI - 1000]
  • what is exactly the case mod == 0b00 & R/M == 0b110? It' just a indirect absolute value, eg mov cl, [1234h], which masm compile as 8b0e3412: mov cx, WORD PTR ds:0x1234
  • are all these indirect addressing always relative to a segment? From the refence it sounds like that in 16 bit mode everything is always relative to DS, unless BP is contained in the indirect address, in that case SS is used (or a specific segment override is used). So basically [BP+SI+10h] always means SS:[BP+SI+10h] where SS segment is shifted by 4 bits to the left.
  • which is the exact role of 67h prefix in this context? If I use a 67h prefix it's like switching the table of 16 bit addressings with the 32 bit addressing and viceversa? (according to the current executing mode).
  • and what about 66h? Does it just change the "size" of data moved between 16 bits and 32 bits? Eg, forcing 32 bit operand size means that a 32 bit register will be selected and always 4 bytes of memory will be fetched from the indirect address and vice versa?

And now the 32-bit addressing modes

32 Addressing modes

  • mod == 0b11: direct value, as for 16 bit, quite clear
  • mod == 0b00 && R/M == 0b101: raw value as for the 16 bits addressing case
  • mod != 0b11 && R/M == 0b100: the R/M doesn't specify a register but the SIB mode, so we can specify a base register + an index register + a scale value

Here everything is enough clear, I was just wondering, as for 16 bits if displacement in 32 bits are signed or unsigned? SIB and displacement can be combined easily if I understand it correctly, eg [EAX + EBX*2 + 10] will generate a mod == 01 with specific SIB byte and additional single byte for signed displacement. Are these values considered absolute in a flat memory space or segments must be considered here too?

1 Answer 1


These are a lot of questions at once, i'll answer at least some of them. But, please not, unless you're writing an assembler or disassembler yourself, you shouldn't really go into the gory details of every single bit. And unless you have done a lot of programming in assembler, and reading and understanding disassembled code, you shouldn't even try to write an assembler or disassembler yourself.

Don't misunderstand me, writing your own assembler can be an interesting and educational experience. But the gory details aren't first thing you should learn to understand the concept of a processor and its assembly language.

That out of the way:

It doesn't matter if the 16 bit displacement is signed or unsigned. If it overflows, it overflows, and it's always cut to 16 bit. So if you add offset 0xfff0 to address 0x1234, you'll get 0x1224 as result. It doesn't really matter if you interpret this as "0xfff0 is equal to -0x0010, so we subtract 0x10 from 0x1234" or "add 0xfff0 to 0x1234, get 0x11224, and strip the overflow bit". Or, if you add 0x89ab and 0x89ab, you get 0x1356. 0x11356 with the overflow bit stripped, to be precise. It doesn't matter if you take 0x89ab as (decimal) 35243, or -30293. The possible results - 35243+35243=70486, 35243-30293=4950, -30293-30293=-60586 - do all have the same representation - 0x1356 - in 16 bit hex.

Yes, mod=0b00 and R/M=0b110 is just an indirect address. mov cx, [1234h] and mov cx, WORD PTR ds:0x1234 are two ways of writing the same thing. Note i corrected your cl to cx; whether or not you use an 8-bit or 16-bit register is part of the instruction, not of the addressing mode. If you have a register, the size is clear from the register name, but in an instruction like mov [1234h],5, you don't know if the 5 is a byte, word, or dword value. mov word ptr ds:1234h, 5 makes this clear.

Yes, all addresses are relative to the chosen segment - ds in most cases, ss if you use bp, and the given register if you use an explicit override prefix. Note there wasn't a way to index relative to sp in 16 bit mode, and bp, if used at all, was always the first register in R1+R2 combinations, forcing ss to be used with bp. In 32 bit mode, more combinations are possible, and [ebp+ebx] uses ss, while [ebx+ebp] uses ds. (However, 32 bit mode also means protected mode, and in all but the most pathological cases, operating systems use the same selector values for ss and ds, and cs as well. See below).

So [BP+SI+10h] means [SS:BP+SI+10h], which means (SS<<4 + BP + SI + 10h) on the address bus lines. Note that those 16 bit processors had 20 bits on the address bus, which means overflow could occur, and the overflow bit was cut off as well. So, FFF0:0010 and 0000:0000 are actually the same address on an 8086 - 00000 - since bit 20 from 100000 got cut off. On a 32 bit processor, this bit 20 actually exists. Which means some programs, that used that mechanism to obfuscate their copy protections, stopped working when the 80386 was introduced. Or would have, if IBM hadn't invented a mechanism around it - the nefarious A20 gate. Google for that if you're inclined to do so.

Prefixes 66h and 67h - ask someone else. Although i've been reading and writing assembler code for more than 20 years, i never had reason to learn the relation between hex bytes and processor instructions. See above. Well, i guess there are two exceptions: 90h is NOP, and cch is INT3. And byte sequences like PQRST, 50h 51h 52h 53h 54h are push-register instructions, which makes them useful for locating procedure starts.

In 32 bit modes, the displacements are just as "signed" or "unsigned" as in 16 bit modes. Just treat them as 32 bit values that get added, which might result in an overflow that's thrown away.

And of course, these values are considered relative to the "segment"s as well. Just that 32 bit implies protected mode, which means the segments are called selectors, have different semantics, and get generally ignored by most application programmers.

A word to segments, why they mattered, and don't (normally) matter any more:

At first, when the 8086 was introduced, it was meant to replace the older 8080 processor (and the Z80, which was from a different company, compatible to the 8080, but better and more successful). The 8080 had 64 KB at a maximum altogether, so programmers had to squeeze everything - code, data, stack - into those 64 KB, and most of the time, a part of these 64 KB was used by hardware, so you had even less.

When the 8086, and the segment registers, were designed, someone at intel probably thought "We're giving people much more space - 64 kb code AND 64 kb data AND 64 kb stack, so programs can be much larger; we can multitask between several programs, the operating system will manage the segment registers to assign space to each program, and every program can be so much bigger than today".

But in fact, programs got much larger quickly, so the idea "segment registers should concern the OS only" wasn't ever used. Instead, programs had to juggle segments on their own, which was a major PITA for everybody from compiler builders to application programmers, and everybody had to learn - and know - about them to get anything done.

When 32 bit processors started, with 4 GB addressable in a linear fashion, segments suddenly became big enough that application programmers didn't have to care about them anymore. These days, juggling segments and assigning them to memory maps is strictly the task of the operating system, and due to protected mode, programs couldn't change them even if they wanted. So, what most operating systems do is provide one single flat block of memory to the program, and have cs, ds, es and ss map to that block identically. Your application just sees 4 GB of addressable memory (not all of that needs to be truly mapped to physical memory however), and it doesn't matter to the application anymore which segment registers it uses - [DS:1234] is the same as [ES:1234] is the same as [SS:1234] is the same as [CS:1234].

The exception to this is the new registers FS and GS, for example, Windows uses FS for Structured Exception Handling, and Linux uses GS for Thread local storage. These segments are NOT mapped to the standard 4 GB block, but an application won't notice, since neither of these registers is ever used without an explicit prefix. (Note ES can not be used in the same way, since instructions like stos[bwd] and movs[bwd] use ES:EDI by default).

  • Thanks for the detailed answer, I really appreciate it and I'll take some time to read it. Actually I'm quite practical with assemblers and I coded fully working assemblers/cpus of simple architectures for emulation purposes (Z80, Mos6502) so I have no problems with the details or the semantics as long as it's explained in a consistent and non-ambigue way. But I've never dug into x86 and now I realize why CISC architectures and backward compatiblity are really double edged swords.
    – Jack
    Nov 30, 2015 at 22:15
  • :) nice to get refreshed guntram blohm wasnt answering on stack overflow when debug.com was the king of the hill
    – blabb
    Dec 1, 2015 at 7:34
  • Everything is quite clear, if I got it right from a reversing/programming point of view the segment registers in protected mode are still used but their values are just indices inside the GDT and LDT and the translation is made internally by the CPU so it's transparent to the process that is running. This means that even in protected mode a mov ax, [0x123]defaults to mov ax, [ds:0x123] but the value inside ds doesn't mean anything with respect to real memory.
    – Jack
    Dec 3, 2015 at 14:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.