tags:

views:

154

answers:

7

This is going to sound super hackish but does anyone know of a way to combine method bodies at runtime in C++? I'm currently on the path of grabbing the address of the functions then memcopy to executable memory but it has the problem of unwanted prolog/epilog.

Essentially I've got a few dozen simple operations that take the same arguments and return nothing and i want to build a function at runtime out of these simple operations.

+3  A: 

Why not use assembly?

Gyuri
since this is at runtime, (correct me if I'm wrong) i would need to do the work of an assembler at runtime, I guess I'm just not interested in that much work.
Hippiehunter
A: 

It's a bit heavyweight, so it may not be what you're looking for, but you could always try using LLVM to JIT your code.

Brian Campbell
+4  A: 

A simple solution to achieve what you want is to store function pointers in an array.
Then you can create at run-time lists of functions pointers (your scripts).
You execute the script by iterating the list and calling the functions.

Nick D
This approach does not avoid the calls.
Gonzalo
+2  A: 

It won't work, but I tell you what I know. You can get the current process counter position with setjmp() and work in the filled out struct you obtain.

If I had to do something like you ask, I would put two setjmp at the beginning and end of the routines, allocate executable memory (is it even possible ? i thought the text section was readonly, but yes... buffer overflow exploits work by executing the stack, so yes, I guess you can, unless you have the NX technostuff) then copy the chunk from the four setjmp obtained brackets. In this process, you are going to have a mess with opcodes that are not relocatable, I guess, like if they refer to absolute address of other opcodes in your assembler code. Of course if you copy, this address must be changed accordingly.

Good luck.

Stefano Borini
@Stefano Borini, right. @Hippiehunter, you might do that on a system made by microsoft or yourself.
EffoStaff Effo
+2  A: 

Templates?

If you have this:

template <int Operations>
struct Foo
{

  static void Do(int a, int b)
  {
    if (Operations & 1)
    {
      /// op 1 
    }

    if (Operations & 2)
    {
      /// op 2
    }
    if (Operations & 4)
    {
      /// op 3 
    }
    //...
  }
};

the optimizer will throw away the blocks that aren't relevant to your specialization, e.g.

Foo<6>::Do(77,88)

will only process the 2nd and 3rd step and not generate code for the 1st op.

(You should check the output of your compiler, but most should be able to. I'm using this for checking selected array properties, worked under VC6 and later compilers like a charm)

peterchen
+1  A: 

For a trully dynamic solution... physically combining them together:

  • Assume your operations take about the same number of instructions.
  • Make sure that they leave the parameters intact (i.e. leave the stack and call registers untouched).
  • Mark the end with a rare instruction.

Now you are ready to build a vector of instructions:

struct Op {
    char code[MAX_OP_SIZE];
    public Op(const void (*op)()) { 
        /* fill code with no-ops */ 
        const char* opCode = (char*)op;
        char* opCodeCopy = &code[0];
        while (*opCode != GUARD_INSTRUCTION) {
            *opCodeCopy++ = *opCode++;
        }
    }
}

std::vector<Op> combinedOperation;

combinedOperation.push_back(op1);
// ...

void (*combinedOpFunc)(params) = void *()(params)&combinedOperation[0]; // not sure about syntax here

combinedOpFunc(data);
// start praying

Another option is to not copy the code at all. This is a variation of Stano Borini's answer using setjmp/longjmp. Instead of having jumps in each method, we set them up once.

If your op looked like:

void op1(paramtype param) { ... }

make it typedef void (*OPTYPE)(paramtype);

void op1(paramtype param, OPTYPE nextop, jmp_buf returnFrame)
{
     // ...
     if (nextop) {
         // place (paramtype+1) into argument slot 2  (assume +1 does pointer arithmetic)
         // update program counter to value of paramtype
     } else {
         longjmp(returnFrame, 0);
     }
}

Then make a list of operations:

std::vector<OPTYPE> ops; ops.push_back(&op1); // ...

and a helper function

void callOps(const std::vector<OPTYPE>& opList, paramtype param)
{
     OPTYPE firstOp = (OPTYPE)&opList[0];
     jmp_buf frame;
     int ret = setjmp(&frame);
     if (ret == 0) firstOp(param, firstOp + 1, frame);
}

and execute it

callOps(ops, opParameter);
antonm
+2  A: 

I can't see any practical use for this, except for the heck of it :-)

So first you should decide for a platform. There is no way in hell you can do this in a cross-platform way. It might actually be quite difficult to do it in a way that works across several compilers, even on the same platform. Then the processor type, of course :-)

Then you should somehow check that the code you are copying can be relocated. Not everything can be moved around as you feel like.

You have to understand very well the calling conventions, so that you make sure you don't mess up the stack.

Know how the prolog/epilog generated by your compiler. You can probably cheat by adding at the beginning and end of the functions some code sequences that does nothing, but you can use as signature then look for (ie. nop; nop; nop; xor ax, ax; nop; push ax; pop ax; nop; nop; nop; ). Make sure is not optimized-out by the compiler :-)

Make sure you can write/execute that code. The modern CPUs and OSes don't normally allow one to write in a code segment, or to execute a non-code segment. So you will have to find out what are the ways to change the rights (100% OS specific).

Then have fun fighting stuff like "Address space layout randomization," "Stack Randomization," "Data Execution Prevention," "Heap Randomization."

Anyway, a lot of work. And pointless, except to enjoy a good challenge and in the process learn some assembly and OS internals. O for proving yourself as "1337," but then, asking on stackoverflow how to do it is not quite "1337," if you ask me :-)

Anyway, good luck.

Mihai Nita
Heap execution Isn't a problem(a few calls and anything is executable) and I've actually got this working in simple cases already. I guess I was getting burnt by non relocatable instructions. But now that its been mentioned the rare instructions or setjmp sounds like a great way to get the memory locations to copy. I'm going to give your response the answer because its the most complete but the full answer looks to require many techniques.
Hippiehunter