In the x86 architecture, what can be done with EAX
but not with ESP
? Forgetting about push
and pop
and call
.

- 328,167
- 45
- 605
- 847

- 183
- 1
- 7
-
3You can store arbitrary values in EAX without your program randomly crashing once an interrupt comes :) – Seva Alekseyev Sep 06 '16 at 01:36
-
esp is general purpose in special conditions: [Why are rbp and rsp called general purpose registers?](https://stackoverflow.com/q/36529449/995714). For example [Windows used it for BitBlt](https://devblogs.microsoft.com/oldnewthing/20180209-00/?p=97995) – phuclv Dec 06 '22 at 01:53
1 Answers
ESP is implicitly used asynchronously by interrupts. In modern OSes, that only applies to the kernel stack, not the user-space stack. Kernel code always needs to keep ESP valid when interrupts are enabled, and assume the space below it is clobbered after every instruction.
The main (only?) asynchronous use of ESP in user-space is signal handlers, so a process with no signal handlers shouldn't have any asynchronous use of ESP. (A kernel can even deliver signals to user-space using a stack other than the thread's current stack pointer, e.g. POSIX sigaltstack
, although if nested signal handling is possible using the alt-stack, signal-handler code can't assume anything). See Is it valid to write below ESP? for a more detailed look at things on Windows, where special cases of SEH can happen anywhere, making it not safe in general to use space below ESP.
Also, debuggers may use the target process's stack when evaluating something like print foo(1)
to call a function in the target program. (But otherwise are non-intrusive, under modern multi-tasking OSes.)
So user-space code can in some cases get away with using ESP as an 8th GP register in a critical loop that otherwise has to spill something, but as that article points out, it makes debugging less convenient on Windows where SEH wants to find a valid stack. Use an MMX or XMM register to save/restore ESP, because static storage wouldn't be thread-safe, and the stack isn't available (chicken/egg problem). The same argument in theory applies to using RSP in 64-bit code, but 15 regs other than RSP, and guaranteed SSE2 support, makes this extremely unlikely to be worth it.
Everything else in this answer applies equally to RSP in 64-bit mode.
ESP limitations as a GP register operand in asm / machine code
There's only one thing ESP can't do that every other register can: ESP can't be the index register in an addressing mode.
mov edx, [esp + eax*4] ; legal
mov edx, [eax + esp*4] ; not encodeable
mov edx, [eax + esp] ; assemblers will encode this with esp as the base reg, since neither reg is scaled.
If I remember correctly, this is the only case where ESP just plain isn't available as an operand. The other special case is that ESP as a base register always requires a SIB byte, even when there's no index:
mov edx, [eax] ; 2 bytes: opcode + ModRM
mov edx, [ebp] ; 3 bytes: opcode + ModRM + disp8=0 (the other addressing-mode limitation, ebp/rbp and r13 as a base reg needs a displacement; the mode+M encoding that would mean this actually mean something else)
mov edx, [esp] ; 3 bytes: opcode + ModRM + SIB
mov edx, [ebp + 4] ; 3 bytes: opcode + ModRM + disp8
mov edx, [esp + 4] ; 4 bytes: opcode + ModRM + SIB + disp8
mov edx, [ebp + 4 + eax] ; 4 bytes: opcode + ModRM + SIB + disp8
mov edx, [esp + 4 + eax] ; 4 bytes: opcode + ModRM + SIB + disp8
pop
into ESP is also a bit special because order of operations between reading the implicit source, the implicit increment of ESP, and writing of the explicit destination matters: pop esp
is like mov esp, [esp]
. (Also, popa
skips reloading ESP). See What is an assembly-level representation of pushl/popl %esp? for details.
It is also worth pointing out that there are a lot of things special about EAX, even compared to other registers like ECX. For example, it is implicitly used with stos
, cdq
, and as an operand for mul
(and this list is not exhaustive). There is also a 1-byte encoding for xchg eax, reg
(great for code golf but not performance!), and for the common ALU operation with an imm32 (like add eax, imm32
vs. add r/m32, imm32
). (Look up these ALU instructions online or the original PDF of Intel's instruction reference manual—see the x86 tag wiki for links.)
The only one of the base 8 general-purpose registers that isn't "special" or used implicitly by any common instructions is EBX. For more info on x86 registers and where their names come from / traditional uses, see http://www.swansontec.com/sregisters.html

- 328,167
- 45
- 605
- 847
-
2`ebx` used to be special in that it is one of the three registers you can use for memory operands in 16 bit code. – fuz Sep 06 '16 at 08:04
-
@Cody: Thanks for the edit, overall it seems like a more coherent order to present that information. You did introduce one mistake though: EBX is used implicitly by [CMPXCHG8/16B](http://www.felixcloutier.com/x86/CMPXCHG8B:CMPXCHG16B.html), so even some modern compiler output will need to save/restore EBX to allow using it for that. It's also used by [XLAT](http://www.felixcloutier.com/x86/XLAT:XLATB.html), which isn't relevant. It's also an output for CPUID. The weasel words "anything important" were intentional :D It is why RBX and RBP are the two call-preserved low-8 regs in 64bit SysV. – Peter Cordes Sep 06 '16 at 09:48
-
@FUZxxl: well four really. Subsets of `[bp/bx + si/di + disp8/disp16]` were the only choices, given that the encoding didn't use a SIB byte. I guess you were excluding BP since it was too inconvenient to use it as anything but a frame pointer (since SP-relative addressing is impossible). – Peter Cordes Sep 06 '16 at 09:50
-
Haha. I was trying to figure out how I changed anything that affected what you had said about EBX, but then I see it was the removal of the weasel words. That they might have been significant didn't even dawn on me at the time. Of course CPUID uses it as an output, but it uses four different GP registers, so that seems like a rather obvious and special exception. I had completely forgotten about CMPXCHG, though. I let a compiler write all of my synchronization code for me! – Cody Gray - on strike Sep 06 '16 at 10:03
-
@CodyGray: I remember that CMPXCHG8B uses EBX more because it's a "fun fact": one of the few implicit uses of EBX; not because of actually using CMPXCHG8B in my own code. (But it comes up every time someone asks SO how to use it from inline asm.) BTW, it's moderately inconvenient to get gcc/clang to make efficient code that uses CMPXCHG16B for compare-and-swap, but will read one of the two 8B halves separately without using CMPXCHG16B as an atomic 16B load, [but I did it](http://stackoverflow.com/a/38991835/224132) :). Agree on letting compilers make the code :) – Peter Cordes Sep 06 '16 at 10:18
-
@PeterCordes I totally forgot about bp. Yeah, I was thinking about bx, si, and di. – fuz Sep 06 '16 at 10:19