I'm testing some memory dumping routines in 8086 Assembly based on the one that Keith of Chibiakumas created for displaying registers and showing memory. This routine is a modified version of his that shows the memory in big-endian 16-bit hexadecimal words to make the call stack easier to read. (The reason I was doing this was to figure out exactly what an INT
does to the stack, but that's not what I'm asking about here.)
My understanding was that the stack on x86-based CPUs grew downward in memory, that is, as more values are pushed onto the stack, the new value is placed earlier in memory than the last. For example, let's pretend that SP = 0x03FA
and the last word pushed onto the stack is 0x021F
. If the word 0xFFFF
is pushed onto the stack, then SP
now equals 0x03F8
and the value 0xFFFF
is stored at 0x03F8
and 0x021F
is stored at 0x03FA
.
This is where I'm running into some weirdness, and I can't tell if my code is faulty or my assumptions about the stack are wrong. The image below represents the output of the memory dump code, which shows the contents of each register. Once that returns, I then call a routine then performs a hexdump of BX
words from ES:BP
. The stack segment register SS
was copied into BP
, then I chose 0x03D0
for the offset (since it was close to the starting stack pointer value of 0x0400
and chose to dump 32 words. I did this twice, but before doing it the second time, I loaded 0xFFFF
into AX
and pushed AX
four times. I was thinking that four 0xFFFF
s would be lower in memory than the lowest value in the stack's memory.
But what I found was the opposite; it appears that new values on the stack are placed at the end, and forcing the others down. (Which as you probably know takes MUCH more work than simply overwriting new values.) Yet, the stack pointer decreased from the first and second register dumps, just as I expected. How can this be?
TL;DR: I can't tell if my code is bugged or if the stack doesn't grow downward after all.
The code for my main program, and the memory dump code (the register display code is correct, I'm sure). The picture below the code is a screenshot of the code's output.
mov ax,@data ;Get address of data segment
mov ds,ax ;load it into DS
mov ax,@code ; uncomment this to load ES with the code segment.
mov es,ax ; otherwise it's loaded with the data segment
cld ;String functions are set to auto-increment
mov ax,2 ;clear screen by setting video mode to 0
int 10h ;select text mode - We're already in it, so this clears the screen
call doMonitor ;show registers
call NewLine ;advances text cursor to next line by printing CR followed by LF
call NewLine
mov ax,ss
mov es,ax ;load stack segment into ES
mov bp,03D0h ;load an offset close to SP's current value
mov bx,20h ;dump enough bytes to see the stack
call doMemDump16 ;dump 32 words starting at 0x03D0
call NewLine
call NewLine
mov ax,0FFFFh
push ax
push ax
push ax
push ax ;push 0xFFFF onto the stack four times, so we can see the stack move
; next time we dump the memory
call doMonitor ; show the registers again, so that we can see SP change
call NewLine
call NewLine
mov ax,ss
mov es,ax
mov bp,03D0h
mov bx,20h
call doMemDump16
mov ax,4C00h
int 21h ;return to DOS
;;;;;;;;;;;;;;;;;;;;; memory dump routine (some subroutines were omitted, those are working fine)
doMemDump16:
push ax
push bx
push cx
mov ax,es ;PRINT THE SEGMENT:OFFSET POINTED TO BY ES:BP
mov al,ah
call Printhex
mov ax,es
call Printhex
mov al,':'
call PrintChar
mov ax,bp
mov al,ah
call Printhex
mov ax,bp
call Printhex
call NewLine
MemDump16Again:
mov ax, [es:bp] ;read in a word from [ES:BP]
xchg ah,al ;print high byte first
call Printhex
xchg ah,al ;then low byte
call Printhex
mov al,' '
call Printchar ;print a space between words
inc bp
inc bp ;next word
dec bx ;decrease counter
mov al,bl ;we need to check whether counter is a multiple of 8 non-destructively
and al,00000111b ;filter by 8s on MS-DOS
jne MemDump16Again
call NewLine ;new line every 8 words printed
cmp bx,0 ;is this the end of the specified data stream?
jne MemDump16Again ;if not, keep dumping
pop cx
pop bx
pop ax
ret