Search code examples
assemblyx86-16bootloadermemory-segmentationstack-pointer

Trouble understanding stack segment register


Segment registers are used to increase the range of addressable memory from 64K to 1M bytes. But, I have trouble understanding stack segment register (SS) because stack already has two other registers associated to it, sp and bp.

Suppose, I have set SS to 5000h and then decided to initialize the stack by initializing bp and sp registers. Initially the stack should be empty. So, both of sp and bp should have same content initially. Can I initialize sp with any random address or Will I have some restriction?

For Example, is it ok to initialize sp with the address 7000h


Solution

  • In x86, the stack is a LastInFirstOut (LIFO) structure where the SS segment register marks the start and the stackpointer SP points directly above the free space on the stack. In memory, the free space is lower than the used space because the stack grows downward. It is this downward expansion that makes talking about the stackpointer as "the top of the stack" confusing because it is counterintuitive for the top to be at the bottom.
    In x86-16, the stack can occupy at most 64KB or 65536 bytes. The SP register which is a 16-bit register can never address anything outside of this stack segment.

    Now if your program initialization has these instructions:

    mov  ax, 5000h
    mov  ss, ax
    mov  sp, 7000h
    

    you are telling that the stack is going to be a chunk of 28672 (7000h) bytes starting at linear address 0005'0000h and ending at linear address 0005'6FFFh. At this point in your program you can say that "the stack is empty". And it would be a severe programming error to eg. pop ax while the SS:SP register pair has 5000h:7000h.

    | 5000h (SS)                                                     | 6000h
    |                                                                |
    |<--------------------------------- 64KB ----------------------->|
    |      This is the stack     |   This is not part of the stack   |
    |                                                                |
    |                            ^                                   |
    | 0                          | SP=7000h                    65535 |
    

    In order to place a new item on the stack (push / call / int), the stackpointer SP is lowered and then the new item is written at that address. For removal (pop / ret / iret) the item where SP points at is read and then SP is raised.

    Let's see that in action:

      mov  cx, 6144
    More:
      push cx
      loop More
    

    Registerwise, only the stackpointer SP has changed.

    | 5000h (SS)                                                     | 6000h
    |                                                                |
    |<--------------------------------- 64KB ----------------------->|
    |      This is the stack     |   This is not part of the stack   |
    |                xxxxxxxxxxxx                                    |
    |                ^                                               |
    | 0              | SP=4000h                                65535 |
    

    Now removing two thirds of it:

      mov  cx, 4096
    More:
      pop  ax
      loop More
    

    Once again, registerwise only the stackpointer SP has changed.

    | 5000h (SS)                                                     | 6000h
    |                                                                |
    |<--------------------------------- 64KB ----------------------->|
    |      This is the stack     |   This is not part of the stack   |
    |                        xxxx                                    |
    |                        ^                                       |
    | 0                      | SP=6000h                        65535 |
    

    We can read/write the stack memory just like any other memory. However because of the segmented nature of memory, ordinarily we would need to use the SS: segment override:

    mov  ax, [ss:6000h]
    
    mov  bx, 6000h
    mov  ax, [ss:bx]
    

    Or we could just make DS refer to the stack segment:

    mov  cx, 5000h
    mov  ds, cx 
    
    mov  ax, [6000h]
    
    mov  bx, 6000h
    mov  ax, [bx]
    

    Here begins the strange case of BP. The designer has made it so that all memory referencing that relies on the BP register, will be relative to the stack segment by default. We can address data in the stack segment without having to specify a segment override or manipulating the DS segment register if we load the offset address in BP:

    mov  bp, 6000h
    mov  ax, [bp]
    

    Other than this 'stickyness' to the stack segment, there's nothing special about BP.