As stated, this is not a useful thing to do, but it's not that far off from what just-in-time compilers do all day, or the OS's executable loader.
- As Carl says, you need to put machine instructions into the buffer, not C or assembly.
- That means you get to do all of the work of complying with the ABI yourself.
- That also means you get no portability at all. Code that does this sort of thing has to be written N times - once for each CPU+OS combination you care about. Just-in-time compilers usually have a fallback to a byte-code interpreter for not-yet-supported platforms.
- For security reasons, you can't just dump machine code into a buffer and jump to it; you have to tell the OS what you are doing.
As it happens I have an example lying around. This is only tested to work on (x86 or x86-64)/Linux. If you wanted to make it do something more interesting, you would need to replace the memset
with code that filled in the buffer with the machine code for a more interesting operation.
It will not work on any other CPU, because it hardwires the x86 machine encoding of the return instruction. It probably won't work on any other x86 OS, either, because it ignores the portability minefield surrounding mmap
and mprotect
:-(
#define _GNU_SOURCE 1
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void __attribute__((noreturn))
perror_exit(const char *msg)
{
perror(msg);
exit(1);
}
int main(void)
{
/* allocate one writable page */
size_t pagesize = getpagesize();
void *rgn = mmap(0, pagesize, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANON, -1, 0);
if (rgn == MAP_FAILED)
perror_exit("mmap");
/* fill it with return instructions */
memset(rgn, 0xC3, pagesize);
/* now switch the page from writable to executable */
if (mprotect((caddr_t)rgn, pagesize, PROT_READ|PROT_EXEC))
perror_exit("mprotect");
/* now we can call it */
((void (*)(void))rgn)();
return 0;
}