In real mode (it looks like you're trying to make programs for it), address is calculated first. The formula is pretty simple:
address = (segment<<4)+offset
Then this address is substracted by size of element you're trying to push (word
: 2 bytes, dword
: 4 bytes, qword
: 8 bytes).
Third step is writing data on resulting position. Therefore if SS
is 0x30
and SP
0xFF
, then:
address = (0x30<<4)+0xFF = 0x3FF
- Element is word, so we substract
2
: position = 0x3FF - 2 = 0x3FD
- Writing data on resulting position (as C pointers):
*(unsigned char*)position = LOBYTE(0x1234)
,
*((unsigned char*)position+1) = HIBYTE(0x1234)
.
So, byte SS:[SP] = 0x34
and byte SS:[SP+1] = 0x12
. Note that stack grows/expands down, so there won't be SP-x
, but SP+x
.
In protected mode, things are bit more complicated (I'll write here only process of working with segment register):
First, processor get's address of Global Descriptor Table Descriptor (which is loaded by LGDT
instruction).
Processor compares value of segment register with size of GDT (if it's greater, exception is thrown). It also checks if segment register isn't pointing to null descriptor (SS!=0
)
- Then processor gets pointer to GDT entries, and calculates offset from start of GDT (using
SS*8
).
Processor must check some things:
4.1. Is segment present?
4.2. Do we have enough priviliges to access segment?
4.3. Is it code segment?
4.4. Is it writable?
4.5. Is ESP*granularity+base < limit
4.6. Is it system segment?
Then it calculates address by base_of_segment+ESP
.
After it it continues similary to real mode.