2013-02-12 2 views
3

Я хотел бы сгенерировать функцию во время выполнения в C. И я имею в виду, что по существу я хотел бы выделить некоторую память, указать на нее и выполнить ее с помощью указателя функции. Я понимаю, что это очень сложная тема, и мой вопрос наивен. Я также понимаю, что есть некоторые очень надежные библиотеки, которые делают это (например, nanojit).Генерирование функций во время выполнения в C

Но я хотел бы изучить технику, начиная с основ. Может ли кто-нибудь знающий дать мне очень простой пример в C?

EDIT:Ответ ниже велик, но здесь тот же пример для Windows:

#include <Windows.h> 

#define MEMSIZE 100*1024*1024 
typedef void (*func_t)(void); 

int main() { 

    HANDLE proc = GetCurrentProcess(); 
    LPVOID p = VirtualAlloc(
     NULL, 
     MEMSIZE, 
     MEM_RESERVE|MEM_COMMIT, 
     PAGE_EXECUTE_READWRITE); 

    func_t func = (func_t)p; 
    PDWORD code = (PDWORD)p; 
    code[0] = 0xC3; // ret 

    if(FlushInstructionCache(
     proc, 
     NULL, 
     0)) 
    { 
     func(); 
    } 

    CloseHandle(proc); 
    VirtualFree(p, 0, MEM_RELEASE); 
    return 0; 
} 
+0

Существует общая проблема, связанная с тем, что стандарт C89/90 явно говорит о том, что преобразование произвольного указателя на указатель функции является неопределенным поведением (*** G.2: *** * Указатель на функцию преобразуется в указатель на объект или указатель на объект преобразуется в указатель на функцию. *) Я был бы удивлен (но заинтересован!), Если бы вы действительно могли сделайте это портативным, стандартно-совместимым способом. – detly

+1

Имейте в виду, что есть существенные недостатки безопасности для генерации кода во время выполнения. Атакующие часто имеют некоторый уровень контроля над данными, проходящими через вашу программу. Если у вас есть маршрут для данных, контролируемых атакующим, чтобы стать управляемым злоумышленником кодом, есть способ для злоумышленника установить вредоносное ПО на ваших компьютерах вашего клиента. Смешайте данные и код на свой страх и риск! – SecurityMatt

+0

Спасибо Мэтту, я знаю, что он рискован и сложный. Это мое понимание, хотя это, как правило, метод, используемый инструментами JIT и, по сути, компиляторами. Пожалуйста, поправьте меня, если я ошибаюсь. –

ответ

4

Как уже было сказано другими плакатами, вам нужно хорошо знать свою платформу.

Игнорирование вопроса о литье указателя объекта на функциональный указатель, технически, UB, вот пример, который работает для x86/x64 OS X (и, возможно, Linux тоже). Весь сгенерированный код - это возврат к вызывающему.

#include <unistd.h> 
#include <sys/mman.h> 

typedef void (*func_t)(void); 

int main() { 
    /* 
    * Get a RWX bit of memory. 
    * We can't just use malloc because the memory it returns might not 
    * be executable. 
    */ 
    unsigned char *code = mmap(NULL, getpagesize(), 
      PROT_READ|PROT_EXEC|PROT_WRITE, 
      MAP_SHARED|MAP_ANON, 0, 0); 

    /* Technically undefined behaviour */ 
    func_t func = (func_t) code; 

    code[0] = 0xC3; /* x86 'ret' instruction */ 

    func(); 

    return 0; 
} 

Очевидно, что это будет отличаться на разных платформах, но в нем описываются основы, необходимые: получить исполняемый раздел памяти, писать инструкции, выполнять инструкции.

+0

совершенный. Благодарю. –

+0

Для записи кажется, что соответствующей функцией в Windows является VirtualAlloc с флагом PAGE_EXECUTE_READWRITE. –

3

Это требует, чтобы вы знали вашу платформу. Например, что такое конвенция вызова C на вашей платформе? Где хранятся параметры? Какой регистр содержит возвращаемое значение? Какие регистры должны быть сохранены и восстановлены? Как только вы это знаете, вы можете по существу написать код C, который собирает код в блок памяти, а затем переводит эту память в указатель функции (хотя это технически запрещено в ANSI C и не будет работать в зависимости от того, будет ли ваша платформа отмечена на некоторых страницах памяти как неисполняемый, так называемый бит NX).

Простой способ это просто написать код, скомпилировать его, затем разобрать и посмотреть, какие байты соответствуют инструкциям. Вы можете написать некоторый C-код, который заполняет выделенную память этой коллекцией байтов, а затем выводит ее на указатель функции соответствующего типа и выполняет.

Возможно, лучше всего начать с чтения calling conventions для вашей архитектуры и компилятора. Затем научитесь писать сборку, которую можно вызвать из C (т. Е. Следует за вызовом).

2

Если у вас есть инструменты, они могут помочь вам сделать некоторые вещи проще. Например, вместо того, чтобы пытаться сконструировать правой функции пролог/эпилог, я могу просто код это в C:

int foo(void* Data) 
    { 
    return (Data != 0); 
    } 

Тогда (MicrosoftC под Windows) кормить его «сл/Fa/с foo.c». Тогда я могу смотреть на «foo.asm»:

_Data$ = 8 
; Line 2 
     push ebp 
     mov  ebp, esp 
; Line 3 
     xor  eax, eax 
     cmp  DWORD PTR _Data$[ebp], 0 
     setne al 
; Line 4 
     pop  ebp 
     ret  0 

Я мог бы также использовать «Dumpbin/все foo.obj», чтобы увидеть, что точные байты функции были:

00000000: 55 8B EC 33 C0 83 7D 08 00 0F 95 C0 5D C3 

Просто экономит я некоторое время получаю байты в точности ...

Смежные вопросы