How to dynamically allocate function code?

后端 未结 2 1198
不思量自难忘°
不思量自难忘° 2021-01-28 06:32

In the C language, the usual route for function pointers as callbacks from some library is to include a void* pointer for the context of the user:

v         


        
相关标签:
2条回答
  • 2021-01-28 07:16

    I think I got an answer.

    The real problem is getting a void* context pointer inside a callback that does not receives one. And I can't use (in principle) global variables because there are potentially many callbacks being called, and I can't identify which is the one.

    I could take the same route as a JIT VM: generating machine code dynamically for each possible void* ctx value, such as:

    void do_stuff(void* ctx) {
      // Finally have the ctx!
    }
    
    void* my_ctx;
    my_fn_ptr p = allocate_function_that_calls_f_with_ctx(do_stuff, ctx);
    library_register_callback_fn(p);
    

    Here p is a function pointer for a function with a signature void (*)(void), that when called in turns it calls f(ctx).

    However, this is just for a callback for a library, and the number of callbacks is small. I could then write a bunch of functions, each one taking a void* from a table, each one from a different index:

    void do_stuff_0(void) { do_stuff(do_stuff_TABLE[0]); }
    void do_stuff_1(void) { do_stuff(do_stuff_TABLE[1]); }
    void do_stuff_2(void) { do_stuff(do_stuff_TABLE[2]); }
    ...
    

    I can write some simple script that writes in a new .c file all the functions that go from 0 to a couple of thousands. Then I would need to keep track which functions are already registered and which ones are not, and provide some mechanism to both pick the next available and free those which are not needed anymore.

    0 讨论(0)
  • 2021-01-28 07:17

    Assuming you wish to allocate the actual function code, rather than the function pointer. The way this is done when writing bootloaders and similar:

    • Be dead certain about the underlying calling convention used by the compiler and ABI. You'll need to know exactly which registers and/or places in the stack frame where everything goes upon function call and return.
    • Generate the assembler code either by writing a function in the C compiler and carefully copy/paste the assembly, or by manually writing the function in assembler.
    • Translate assembler to op codes.
    • Ensure there's a chunk of memory where you can both store data and execute code. This is also up to the ABI and the MMU setup.
    • Find out how to allocate memory in this custom segment. Typically involves some linker script fiddling and various non-standard keywords. #pragma __declspec __attribute__ blabla with sugar on top. Highly compiler-specific.
    • Allocate memory to this area as a raw uint8_t func [n] array, containing the raw OP codes in hex.
    • Shut off strict aliasing and other such evil pointer conversion things. Ensure that your compiler has some well-defined, non-standard extension when going from object pointers to function pointers.
    • Call the code through ((func_ptr)func) ().

    As you hopefully can tell, this can be quite a task, depending on system. For small microcontrollers using embedded systems compilers, it is somewhat easily achievable. For more intricate systems like x86 or PowerPC, using compilers like gcc, far less so. You'll be relying on various poorly-specified behavior and the code will be completely system-specific.

    0 讨论(0)
提交回复
热议问题