Is the instruction after RET always after CALL?

In a well-managed C program, does the return statement (RET) always return to the statement after the CALL statement? I know this is the default value, but I would like to check if anyone knows or remembers genuine examples of cases where this standard is not applied (normal compiler optimization or other things ...). Someone told me that this could happen with a function pointer (a function pointer would push the value onto the stack, not CALL ... I searched for it but didn't see anything).

Let me try to better explain my question. I know that we can use other structures to change the execution thread (including stack control) ... I understand that if we change the return address written on the stack, the thread will change to the address that was written on the stack. I need to know if there is any unusual execution situation when the next instruction is not the one that follows the CALL? I would like to be sure that this will not happen if something unexpected does not happen (for example, a violation of access to memory, which will lead to a structured exception handler).

My concern is whether commercial applications are ALWAYS following the pattern above. Please note that in this case I have a fix for exceptions (it is important to know if they exist in this case, for a research project I will move to the discipline of the M. Sc. Program). I know, for example, that the compiler can sometimes change RET to JMP (tail call optimization). I would like to know if something like this can change the order of an instruction that is executed after RET and mainly if CALL will always be immediately before the command executed after RET.

+4
source share
6 answers

"Good Work" Program C can be translated by the compiler into a program that does not follow this pattern. For example, to obfuscate code, you can use the push / ret combination instead of jmp.

+1
source

Eliminating virtual memory situations (when RET can cause a page error, which technically means that RET triggers are an error handler), I think the main thing that is worth discussing is that setjmp and longjmp can be completely deflected by the stack - so you can legally call something, and then discard an arbitrary number of stack frames without pressing RET.

I assume that it is possible that the longjmp implementation may include a RET with a modified stack - the developer should have understood how they wanted to implement this.

0
source

In a well-managed C program, the return statement (RET) always returns to the statement following the CALL statement?

This is illogical, because there is nothing that requires calling a function and returning from it to necessarily match these instructions, although, of course, this is quite common. One example of this is the function in which the function is embedded.

I think it would be very unusual if the x86 targeting compiler was packing things, so the ret statement matching the return was somewhere other than the address following the call statement. But something that I think can sometimes occur on an ARM processor.

Since the ARM command cannot always contain the full 32-bit data of the immediate data, for constants (numeric or string) it is “embedded” as data in the code stream, so the value or pointer to it can be loaded using the relative address pc (program counter). Usually these constants are located in a place where the jump does not need to be done just because of the data. One of the most common places for such data will be the area between the code for two functions. But there is one more place where this condition is satisfied after the branch is made to call the function, since the branch must be taken in any case to go to the instructions following the call site (return from the function). Thus, it does not hurt the runtime to place the data immediately after the call and set the return address as the address that follows the data. The compiler loads the lr register (which is used by the agreement to store the return address) with the address following the data, and then returns an unconditional function branch. You may not see this too often, but similar methods for placing data in a code segment are common to ARM.

0
source

The address of the CALL routine is equivalent to the address of the next PUSH instruction + the address of the JMP routine .

At the same time, the PUSH address is almost equivalent to SUB xSP, pointer size + MOV [xSP], address .

SUB xSP, pointer size can be replaced with PUSH .

RET is almost equivalent to JMP [xSP] , followed by ADD xSP, the address of the pointer at the location where the JMP leads.

And ADD xSP, the pointer address can be replaced with POP .

So you can see what basic freedom the compiler has. Oh, by the way, it can optimize your code so that your function is fully integrated, and there is no call or return from it.

While somewhat perverse, it is not possible to make much more frightening control transfers using instructions and methods very specific to the platform (CPU and OS).

To transfer control, you can use IRET instead of CALL and RET if you push the appropriate things on the stack for instructions.

You can use Windows Structured Exception Handling so that the command that throws the CPU exception (e.g. division by 0, page error, etc.) distracts the execution from your exception handler, and from there control can be transferred either back to the same instructions to either the next or next exception handler or to any location. And most x86 instructions can cause processor exceptions.

I am sure there are other unusual ways to control the transfer, inside and inside routines / functions.

You can often see code similar to this:

 ... CALL A A: JMP B db "some data", 0 B: CALL C ; effectively call C with a pointer to "some data" as a parameter. ... C: ; extracts the location of "some data" from the stack and uses it. ... RET 

Here, the first call is not a subroutine, it's just a way to populate the address of data stuck in the middle of the code.

This is probably a programmer, not a compiler. But I could be wrong.

What I'm trying to say with all of this is that you should not expect CALL and RET be the only way to enter and exit subprograms, and you should not expect that they will be used for this purpose and balance each other .

0
source

Theoretically, the compiler could, given the following code:

 return f(), g(); 

create assembly line by line:

 push $g jmp f 
0
source

May be. Some processors have something called a “delay slot” (sometimes two) that are instructions, directly following branch instructions (including CALL), which execute as if they were at the branch’s target. This obvious nonsense was added to improve performance, as the prefetcher of commands quite often got ahead of the branch instructions by the time it understands that there is a branch. The address pressed by CALL as the return address is not the address following CALL, if there are time slot instructions, the return address is the address following the delay instructions (s).

http://en.wikipedia.org/wiki/Delay_slot

This led to the complexity of the instruction set architecture (ISA) for this machine, for example, what happens if you place branches in the delay slots, what happens if the instruction in the delay slot causes an error? What happens if there is a trap (like a single trap)? You can see that this is getting messy ... but an amazing amount of old RISC processors have ones like MIPS, SPARC and PA-RISC.

0
source

Source: https://habr.com/ru/post/1400654/


All Articles