say:
# EIP is the 32bit Instruction Pointer, aka program counter "PC"
# The EIP register contains the address of the next instruction
# to be executed if no branching is done.
# EIP can only be read through the stack after a call instruction.
movl 0(%esp), %edx # load PC_next from the return_addr on the stack
pushl %ebx
call print16_hex32
call print16_nl
movl %edx, %ebx # ch_put(%dl)
say_loop:
movb (%ebx), %dl
cmpb $EOS, %dl
je say_done
call ch_put
incl %ebx # increment the string pointer
jmp say_loop
say_done:
movl %ebx, %edx # string_len
popl %ebx
incl %edx # pc_next = string_len +1
call print16_hex32
call print16_nl
movl %edx, 0(%esp) # save PC_next as return_addr on the stack
ret
call say
.string "################## hAllo ########################\n"
my-mon > load_elf 0x00100000
00100000: b2 40 e8 a6 00 00 00 e8 7a 01 00 00 e8 ce 00 00 | .@......z.......
00100010: 00 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 | .###############
00100020: 23 23 23 20 68 41 6c 6c 6f 20 23 23 23 23 23 23 | ### hAllo ######
00100030: 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 | ################
00100040: 23 23 0a 00 ba fe ca ad de e8 8e 01 00 00 e8 33 | ##.............3
00100050: 01 00 00 ba 00 10 10 00 e8 7f 01 00 00 e8 24 01 | ..............$.
00100060: 00 00 e8 0b 01 00 00 e8 1a 01 00 00 ba 0c 10 10 | ................
00100070: 00 e8 66 01 00 00 e8 0b 01 00 00 e8 f2 00 00 00 | ..f.............
entry position: 0x00100000
my-mon > exec
executing from 0x00100000 ...
0010 0011
################## hAllo ########################
0010 0044
... returning to my-mon
my-mon >
I recommend that you keep call/return always paired
I recommend that you keep call/return always pairedon x86/32, you cannot access EIP directly, the best you can do is read the PC_NEXT from the stack after a function call :-//
The "return" instruction is basically an indirect jump, whose destination address can be found on the top of the stack. At the end it does not matter, how this address was stored in the stack slot. Nevertheless, it still makes a subtle difference.
Note that the "return" instruction immediately starts speculative execution at the predicted address, before the actual destination address has been fetched from the top of the stack. Aim is of course to prevent a pipeline stall. Once the actual destination address has been fetched from the stack, it can be verified whether the prediction was correct or not. If the destination address was mis-predicted, then speculative execution must be rolled back, and execution is restarted at the correct address. And for a "return" instruction, the predicted destination address is basically the return address of the lastly executed "call" instruction.
If the "return" instruction was preced by a corresponding "call" instruction, then "return" will most likely predict the address correctly.
However, nobody hinders you to push an arbitrary destination address onto the stack and invoke "return" in order to jump indirectly to this address. But then "return" is no longer paired with a matching "call", and it will likely mis-predict the address, which has the above mentioned consequences. At the end it will still do the right thing, but with a performance penalty.