Aside from all the other problems, I don't think anyone has yet mentioned that code in its final form in memory cannot in general be relocated. Your example foo
function, maybe, but consider:
int main(int argc, char **argv) {
if (argc == 3) {
return 1;
} else {
return 0;
}
}
Part of the result:
if (argc == 3) {
401149: 83 3b 03 cmpl $0x3,(%ebx)
40114c: 75 09 jne 401157 <_main+0x27>
return 1;
40114e: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp)
401155: eb 07 jmp 40115e <_main+0x2e>
} else {
return 0;
401157: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%ebp)
40115e: 8b 45 f4 mov -0xc(%ebp),%eax
}
Note the jne 401157 <_main+0x27>
. In this case, we have an x86 conditional near jump instruction 0x75 0x09
, which goes 9 bytes forward. So that's relocatable: if we copy the code elsewhere then we still want to go 9 bytes forward. But what if it was a relative jump or call, to code which isn't part of the function that you copied? You'd jump to some arbitrary location on or near your stack.
Not all jump and call instructions are like this (not on all architectures, and not even all on x86). Some refer to absolute addresses, by loading the address into a register and then doing a far jump/call. When the code is prepared for execution, the so-called "loader" will "fix up" the code by filling in whatever address the target ends up actually having in memory. Copying such code will (at best) result in code that jumps to or calls the same address as the original. If the target isn't in the code you're copying that's probably what you want. If the target is in the code you're copying then you're jumping to the original instead of to the copy.
The same issues of relative vs. absolute addresses apply to things other than code. For example, references to data sections (containing string literals, global variables, etc) will go wrong if they're addressed relatively and aren't part of the copied code.
Also, a function pointer doesn't necessarily contain the address of the first instruction in the function. For example, on an ARM processor in ARM/thumb interworking mode, the address of a thumb function is 1 greater than the address of its first instruction. In effect, the least significant bit of the value isn't part of the address, it's a flag to tell the CPU to switch to thumb mode as part of the jump.