in ARM the return address is not pushed on the stack by the caller. Instead, it it always saved in the link register (lr). The callee is responsible for saving the return address (generally the callee will save it on the stack).

When a subroutine is being called, the return address is being preserved in the Link Register. This is done with a Branch with Link (BL) or Branch with Link and Exchange (BLX) instruction. But what if this subroutine calls another function? The Link Register would be overwritten and the program would not find its way back to the previous function. The way this is handled is by preserving the return address on the stack with a PUSH instruction. The PUSH instruction stores the register it is given (in this case LR: push {LR}) to the top of the stack before overwriting the register LR with the new return address.

The same logic is used for registers that are being reused in a subroutine but need to keep track of the original value. In the next graphic you can see that the caller function uses register R3 as a counter, for example. The subroutine happens to require R3 for its own purposes and overwrites R3 with a different value and processes it in some way. To preserve the original value of R3, it gets pushed onto the stack at the beginning of the subroutine, changed and processed, and then restored to its original value by loading it from the top of the stack back to R3 with a POP instruction.


🌱 Back to Garden