Reputation: 207
I'm looking for an easy way of pusha
and popa
in my assembly program. I want to use emu8086 to find bugs in my programs so .286
is not allowed.
I tried:
push_a proc
push ax
push cx
push dx
push bx
push sp
push bp
push si
push di
push_a endp
pop_a proc
pop di
pop si
pop bp
pop sp
pop bx
pop dx
pop cx
pop ax
pop_a endp
But it doesn't work, thus when we call push_a
we push to the stack the address where we are now.
Is there another easy, simple way? I don't want to write eight push
and eight pop
every time.
Upvotes: 1
Views: 2327
Reputation: 39166
I'm looking for an easy way of
pusha
andpopa
in my assembly program.
and
I don't want to write eight
push
and eightpop
every time.
This is exactly what you will be doing when you use the proposed MACRO solution! Each time you use the name of the macro, the entire block of code that it represents is substituted. This truly will be a waste of space.
Although the answer by @MargaretBloom is excellent in explaining all the problems with push sp
and pop sp
, it doesn't give you the easiest solution. Moreover in the context of preserving and restoring all the general purpose registers, pushing and popping the stackpointer is a silly operation. Even the manufacturer (Intel) knows this and cleverly bypasses the pushed SP
value when performing popa
. The SP
register is pushed by pusha
only because it simplifies the hardware design.
The best solution then that fulfills the requirements temporarily removes the return address from call push_a
/ call pop_a
before pushing / popping the 7 useful GPRs. Directly before executing ret
the return address is put back on the stack. Voilà.
push_a proc
pop word ptr [TEMP]
push ax
push cx
push dx
push bx
push bp
push si
push di
push word ptr [TEMP]
ret
push_a endp
pop_a proc
pop word ptr [TEMP]
pop di
pop si
pop bp
pop bx
pop dx
pop cx
pop ax
push word ptr [TEMP]
ret
pop_a endp
This solution also does not alter any of the flags for its operation. Was observed by Fifoernik.
Here's an idea: Why not preserve the FLAGS as an eighth register?
One final note. To make this code really robust you could put this TEMP variable in the code segment of your program and address it with an explicit CS:
override prefix. Your DS
segment register might not always be in the correct place when you need to use your new push_a / pop_a calls. Your CS
register inherently will be correct. If it weren't, the call
would not even function.
Upvotes: 2
Reputation: 44046
One could implement the procedures as macros
pusha MACRO
push ax
push cx
push dx
push bx
push sp
push bp
push si
push di
pusha ENDM
popa MACRO
pop di
pop si
pop bp
pop sp
pop bx
pop dx
pop cx
pop ax
popa ENDM
but this is not the architectural behaviour of pusha
/popa
and it is broken code on a real 8086.
pusha
The push sp
in pusha
should push the state of the stack pointer at the start of the pusha
macro.
Temp ← (SP);
Push(AX);
Push(CX);
Push(DX);
Push(BX);
Push(Temp);
Push(BP);
Push(SI);
Push(DI);Intel pseudo-code for
pusha
- Intel Manual 2B
Using a scratch memory location
You can do something similar if you have a scratch memory location:
pusha MACRO
mov WORD PTR [pusha_scratch], sp
push ax
push cx
push dx
push bx
push WORD PTR [pusha_scratch]
push bp
push si
push di
pusha ENDM
Making sure that the pusha_scratch
is accessible through DS
under any context can be quite of an endeavour.
Not accounting for a separate definition of pusha_scratch
.
Using a position independent, in-place, version
Alternatively one could account for the modified value of sp
with something that is position independent and operates in-place.
If I did my math correctly - double check it - this code should do the trick
pusha MACRO
push ax
push cx
push dx
push bx
push bp ;Top of stack = BP
mov bp, sp ;BP points to TOS
lea bp, [bp+0ah] ;BP is equal to the starting value of SP
xchg bp, [bp-0ah] ;BP = Original BP, [SP] = Starting value of BP
push bp
push si
push di
pusha ENDM
Many thanks to Fifoernik for correcting the broken code by noting the inverted math and preventing the altering of the EFLAGS register.
The nice thing about the snippet above is that it avoids using push sp
entirely as it is a bit problematic when it comes to backward compatibility.
push sp
On a real 8086 the instruction push sp
behaves differently:
The P6 family, Pentium, Intel486, Intel386, and Intel 286 processors push a different value on the stack for a
PUSH SP
instruction than the 8086 processor.
The 32-bit processors push the value of the SP register before it is decremented as part of the push operation;
the 8086 processor pushes the value of the SP register after it is decremented.Intel compatibility section - Intel Manual 3 - 22.17
I don't know how accurate emu8086 is as an emulator of the 8086 but I would avoid push sp
.
popa
The peculiar way pusha
is implemented calls for a peculiar implementation of popa
.
On a real 8086 a naive list of pop
s breaks as soon as pop sp
is executed.
Indeed the CPU exploits the fact that you can't really modify sp
even after a pusha
- otherwise there would be no way to get those values back - and notices that after all the pop
s the original value is implicitly restored.
DI ← Pop();
SI ← Pop();
BP ← Pop();
Increment ESP by 2; (* Skip next 2 bytes of stack *)
BX ← Pop();
DX ← Pop();
CX ← Pop();
AX ← Pop();Intel pseudo-code for
popa
- Intel Manual 2B
So a more correct implementation of popa
is
popa MACRO
pop di
pop si
pop bp
add sp, 02h ;Beware, this affects EFLAGS
pop bx
pop dx
pop cx
pop ax
popa ENDM
Note that you must avoid pop sp
in a real 8086 as a push sp
followed by a pop sp
is not idempotent, for
The
POP ESP
instruction increments the stack pointer (ESP) before data at the old top of stack is written into the destination.Intel description for
pop
- Intel Manual 2B
Finally, one would expect pusha
/popa
to be atomic with respect to interrupts, so a cli
/sti
pair is needed around the macro bodies.
If you use pusha
/popa
just as a shorthand for saving all the registers, then all the hassle can be skipped and a sufficient implementation is
push_all MACRO
push ax
push cx
push dx
push bx
;NOTE: Missing SP
push bp
push si
push di
push_all ENDM
pop_all MACRO
pop di
pop si
pop bp
;NOTE: Missing SP
pop bx
pop dx
pop cx
pop ax
pop_all ENDM
Upvotes: 7
Reputation: 207
pushuj MACRO
push ax
push cx
push dx
push bx
push sp
push bp
push si
push di
ENDM
and when i want to call it i write:
pushuj
I it good?
Upvotes: -1