5 ms·
Yeah, in i386 syntax there is no way to address EIP directly. poping EIP from call is a common trick. In newer processors there exists a cache for return addre
by majke 3y ago
Yeah, in i386 syntax there is no way to address EIP directly. poping EIP from call is a common trick.
In newer processors there exists a cache for return addresses Return Stack Buffer (RSB).
But there is a penalty for doing call and never doing ret.
From Intel's Optimization Reference Manual:[2]
"The return address stack mechanism augments the static and dynamic predictors to optimize specifically for calls and returns. It holds 16 entries, which is large enough to cover the call depth of most programs. If there is a chain of more than 16 nested calls and more than 16 returns in rapid succession, performance may degrade.
[...] To enable the use of the return stack mechanism, calls and returns must be matched in pairs. If this is done, the likelihood of exceeding the stack depth in a manner that will impact performance is very low.
This trick is also what I understand retpolines are about:
Citing kernel doc
The kernel can protect itself against consuming poisoned branch target buffer entries by using return trampolines (also known as "retpoline") for all indirect branches. Return trampolines trap speculative execution paths to prevent jumping to gadget code during speculative execution. x86 CPUs with Enhanced Indirect Branch Restricted Speculation (Enhanced IBRS) available in hardware should use the feature to mitigate Spectre variant 2 instead of retpoline. Enhanced IBRS is more efficient than retpoline.
retbleed https://lwn.net/Articles/901834/ https://lwn.net/Articles/901834/
[2] https://discourse.llvm.org/t/is-pic-code-defeating-the-branch-predictor/18177 https://discourse.llvm.org/t/is-pic-code-defeating-the-branc...
- pjc50 3y ago> there is no way to address EIP directly In general this is a "thing" on pipelined processors, especially once you have out-of-order, because "current instruction" starts to smear out across a bunch of instructions. But for CALL the processor has to dump the pipeline and pick a specific return address to put on the stack. (unless you're MIPS with the branch delay slot nonsense)
- planede 3y agoMy impression was that pipelined processors pipeline across CALL instructions. They even speculate across indirect calls. How you describe this is as if there was a significant penalty to reading EIP or calling a function on a pipelined processor, but I don't think that's true.
- phire 3y agoDon't forget, the CPU needs to know the current PC for relative branch instructions too. Relative branch instructions and calls are pretty common, so flushing the entite pipeline just to get the current PC would be way too expensive. So pipelined CPUs actually implementate extra resources to track the current PC of every single instruction (at least as far as the execute stage) just so they can get the PC rapidly. And you will find almost every single modern CPU has an instruction to copy the current PC to a register.
- titzer 3y ago> has to dump the pipeline Modern Intel processors have a "stack engine" (similar to the return stack buffer) that speeds up access through RSP. But regardless, there's no need to dump any pipeline; a call just has an implicit store to memory and the RSP gets updated (register-renamed, really). Calls are very, very fast these days.
- adrian_b 3y agoThere was no way in 80386, except by pushing EIP on the stack within a CALL. In 64-bit Intel/AMD CPUs, the instruction LEA (load effective address), with an address relative to RIP, can be used to save the instruction pointer in a register, then a jump to any address will arrive there providing the old instruction pointer in the register, ready to be used without popping it from the stack. In all modern pipelined CPUs there are multiple copies of the instruction pointer, one that runs ahead providing the instruction fetch addresses, and an older value that provides base addresses for relative jumps and for relative loads or stores.
- ajross 3y ago> In general this is a "thing" on pipelined processors It's actually not? Or, indeed, it's a hard problem. And modern designs certainly don't expose the instruction counter as a general register anymore (ARM32 PC is the last of its kind). But having an IP-relative data addressing mode is a critical feature for any reasonable modern device, for exactly the reasons detailed here: you want constants stored with your compiled code without having to incur overhead (c.f. the linked article, or the GOT/PLT indirection in shared libraries, etc...) to get it.
- sweetjuly 3y agoThis is generally not true. While OOO processors generally don't love passing PC around (and they usually don't since most instructions won't need it), both RISC-V, ARMv7, and ARM-v8 all provide mechanisms to access PC: ARMv7: you can just use PC directly as a register ARMv8: adr Rd, #0 will move PC into Rd RISC-V: auipc Rd, 0 will move PC into Rd Usually what implementers tend to do to avoid piping around massive 64-bit addresses for no good reason is that they form fetch groups (a linear series of instructions starting at a base PC) and then number instructions both by their group ID and their position in the group. The base PC for each group is then stored in an array indexed over the fetch group. If an instruction later needs its PC (such as to perform a branch or because it caused an exception), a request is sent to the fetch group array to fetch the base PC for the operation which can then be used in conjunction with the offset to reconstruct the original PC.
- im3w1l 3y ago> In newer processors there exists a cache for return addresses Return Stack Buffer (RSB). But there is a penalty for doing call and never doing ret. I think you could play nice by actually doing the ret instead of poping EIP. Something like GET_EIP: mov eax, [esp] ret And then call GET_EIP