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).