Как сохранить и вызвать скомпилированную функцию в C / C++?

Vim (но не исходная Vi!) имеет вкладки, которые я нахожу (во многих контекстах) выше буферов. Можно сказать :tabe [filename] для открытия файла на новой вкладке. Циклическое повторение между вкладками сделано путем нажатия на вкладку или сочетаниями клавиш [ n] gt и gT. Графический Vim даже имеет графические вкладки.

5
задан Joseph Garvin 20 July 2009 в 19:20
поделиться

12 ответов

Имейте в виду, что большинство современных архитектур поддерживают защиту от невыполнения для страниц памяти, и операционные системы с некоторым пониманием используют ее в целях безопасности. Это означает, что вы не можете использовать, например, стековую память или произвольную память malloc для хранения этого кода; вам необходимо настроить разрешения, например, с помощью VirtualProtect в Windows или mprotect в Unices.

12
ответ дан 18 December 2019 в 05:17
поделиться

Звучит как работа для нескольких хороших операторов goto и встроенной сборки.

Просто будьте осторожны.

0
ответ дан 18 December 2019 в 05:17
поделиться

Если вас интересует только C ++, подумайте об использовании «функциональных объектов» - определите класс с одним методом, содержащим логику вашей функции, и оператор перегрузит оператор () в классе, чтобы вызвать метод . Затем, объявив экземпляр вашего класса:

Foo foo;

... вы можете сделать это:

rtn_type result = foo( /* arg list */);

Синтаксически это похоже на сохранение вашей функции в переменной.

0
ответ дан 18 December 2019 в 05:17
поделиться

Эээ, в данном конкретном случае вы , по сути, используете указатели на функции. Началом буфера, содержащего различные функции , является указатель функции.

Лучше всего записать эту часть в сборку, а затем связать ваш файл C с процедурами сборки на этапе компиляции / компоновки. C просто не поддерживает такого рода работу без серьезного взлома ... сборки.

1
ответ дан 18 December 2019 в 05:17
поделиться

Почему бы не объявить рассматриваемые функции встроенными ? Это позволит разместить код вашей функции на месте без ущерба для читабельности. (Хотя и с некоторым влиянием, когда дело доходит до отладки на низком уровне скомпилированного кода)

(Как правильно указал Тайлер, компилятор может проигнорировать это - я писал, исходя из предположения, что флаги оптимизации для запуска компилятора для встраивания кода уже использовались).

В зависимости от флагов оптимизации и вашего кода, компилятор может выполнять некоторые другие оптимизации помимо встраивания кода.

Let ' s говорят, что у нас есть (совершенно бессмысленный, fwiw) код:

#include <stdio.h>

static int test = 0;

int inline foo(int a) {
  test = 0x1234;
  return a + 0x1234;
}

int inline bar(int a) {
  test = 0x5678;
  return a + 0x5678;
}

int inline baz(int a) {
  test = 0x1357;
  return a + 0x1357;
}

int main(int argc, char*argv[]) {
  int i = getchar();
  int j = 0;
  switch(i) {
    case 'a': j = foo(i);
        break;
    case 'b': j = bar(i);
        break;
    case 'c': j = baz(i);
        break;
    default:
        j = 0;
  }
  printf("j: %d, test: %d\n", j, test);
}

Компилируя его на gcc 4.1.1 с -O2 и выполняя objdump -d для полученного исполняемого файла, мы получаем следующий фрагмент для main ():

08048480 <main>:
 8048480:   8d 4c 24 04             lea    0x4(%esp),%ecx
 8048484:   83 e4 f0                and    $0xfffffff0,%esp
 8048487:   ff 71 fc                pushl  -0x4(%ecx)
 804848a:   55                      push   %ebp
 804848b:   89 e5                   mov    %esp,%ebp
 804848d:   51                      push   %ecx
 804848e:   83 ec 14                sub    $0x14,%esp
 8048491:   a1 1c a0 04 08          mov    0x804a01c,%eax
 8048496:   89 04 24                mov    %eax,(%esp)
 8048499:   e8 b2 fe ff ff          call   8048350 <_IO_getc@plt>
 804849e:   83 f8 62                cmp    $0x62,%eax
 80484a1:   74 31                   je     80484d4 <main+0x54>
 80484a3:   83 f8 63                cmp    $0x63,%eax
 80484a6:   74 3d                   je     80484e5 <main+0x65>
 80484a8:   31 d2                   xor    %edx,%edx
 80484aa:   83 f8 61                cmp    $0x61,%eax
 80484ad:   8d 76 00                lea    0x0(%esi),%esi
 80484b0:   74 44                   je     80484f6 <main+0x76>
 80484b2:   a1 24 a0 04 08          mov    0x804a024,%eax
 80484b7:   89 54 24 04             mov    %edx,0x4(%esp)
 80484bb:   c7 04 24 e8 85 04 08    movl   $0x80485e8,(%esp)
 80484c2:   89 44 24 08             mov    %eax,0x8(%esp)
 80484c6:   e8 95 fe ff ff          call   8048360 <printf@plt>
 80484cb:   83 c4 14                add    $0x14,%esp
 80484ce:   59                      pop    %ecx
 80484cf:   5d                      pop    %ebp
 80484d0:   8d 61 fc                lea    -0x4(%ecx),%esp
 80484d3:   c3                      ret
 80484d4:   b8 78 56 00 00          mov    $0x5678,%eax
 80484d9:   ba da 56 00 00          mov    $0x56da,%edx
 80484de:   a3 24 a0 04 08          mov    %eax,0x804a024
 80484e3:   eb cd                   jmp    80484b2 <main+0x32>
 80484e5:   b8 57 13 00 00          mov    $0x1357,%eax
 80484ea:   ba ba 13 00 00          mov    $0x13ba,%edx
 80484ef:   a3 24 a0 04 08          mov    %eax,0x804a024
 80484f4:   eb bc                   jmp    80484b2 <main+0x32>
 80484f6:   b8 34 12 00 00          mov    $0x1234,%eax
 80484fb:   ba 95 12 00 00          mov    $0x1295,%edx
 8048500:   a3 24 a0 04 08          mov    %eax,0x804a024
 8048505:   eb ab                   jmp    80484b2 <main+0x32>
 8048507:   90                      nop

Итак, компилятор уже вставил «необходимые goto () s». В качестве побочного эффекта в этом случае он также уже произвел математику добавления 0x1234 / 0x5678 / 0x1357 по мере необходимости - так что результаты - простые ходы.

2
ответ дан 18 December 2019 в 05:17
поделиться

Чтобы действительно получить машинный код для ваших функций в автоматическом режиме во время процесса сборки, я предлагаю поместить их в отдельный файл .c, а затем скомпилировать его в независимый от позиции код в объектный файл:

gcc -Wall -c -fPIC functions.c -o functions.o

Затем используйте objdump, чтобы выгрузить разборку:

objdump -d functions.o

Выбросьте все, кроме шестнадцатеричных кодов, поместите их в структуру C с помощью xdd -i.

Обратите внимание, что у вас будут довольно серьезные ограничения в этом коде, если вы не хотите иметь дело с перемещением - нет доступа к переменным со статической продолжительностью хранения, нет доступа к внешним функциям. Лучше всего проверить с помощью objdump -r, чтобы убедиться, что перемещение не требуется.

1
ответ дан 18 December 2019 в 05:17
поделиться

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

Это грязный прием и, вероятно, ломается при определенных обстоятельствах / компиляторах. Однако, если вы находитесь в контролируемой среде и это работает на вас, это может помочь вам.

#include <iostream>
#include <vector>

using namespace std;

int multiply( int arg )
{
    return arg * 2;
}

int main()
{
    // Show that multiply apparently multiplies the given value by two.
    cout << multiply( 13 ) << endl;

    // Copy the bytes between &multiply and &main (which is hopefully the multiply code) into a vector
    vector<char> code( (char*)&multiply, (char*)&main );

    // Optimize it by making multiply always return 26
    code[0] = 0xB8;  // mov eax, 1Ah
    code[1] = 0x1A;
    code[2] = 0x00;
    code[3] = 0x00;
    code[4] = 0x00;
    code[5] = 0xC3;  // ret

    // Call the faster code, prints 26 even though '3' is given
    cout << ((int (*)(int))&code[0])( 3 ) << endl;
}
5
ответ дан 18 December 2019 в 05:17
поделиться

Если вы собираетесь компилировать и оптимизировать код во время выполнения, я бы рекомендовал взглянуть на LLVM . ffcall также является полезным пакетом. Обратите внимание, что оба из них решают такие проблемы, как обеспечение того, чтобы память, выделенная для функции, была исполняемой, и что кеш инструкций очищен должным образом для большого количества архитектур. Вы же не хотите изобретать велосипед.

8
ответ дан 18 December 2019 в 05:17
поделиться

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

При этом, если у вас есть указатель на буфер, содержащий вашу функцию, просто приведите указатель на этот буфер к нужному типу указателя функции и вызовите его. Конечно, если ваша операционная система / процессор поддерживает это, вам также придется сбросить флаг NX, который не позволяет запускать неисполняемый код.

Кроме того, вы не можете предполагать, что вы можете копировать буферы, содержащие исполняемые данные, вокруг как если бы они содержали обычные данные - вам придется исправить переходы, переходы и другие обращения к памяти.

РЕДАКТИРОВАТЬ: Я понимаю, к чему вы клоните, но не думаю, что вы сможете найти совместимый со стандартами способ поместить функцию в буфер - C ++ очень мало говорит о структуре памяти, а место хранения функций довольно абстрактное понятие. Вы можете застрять с кучей хаков или использовать отдельные файлы сборки, которые могут быть собраны во время компиляции и загружены из файлов во время выполнения.

С другой стороны, я думаю, вы можете обнаружить, что это быстрее выполнить двойное разыменование при хранении указателя функции (вероятно, 4-8 байтов), чем страдать от промахов кеша при хранении буфера, способного хранить самую большую функцию, которая вам понадобится. Ваша структура с присоединенной функцией имеет много общего с виртуальными функциями в классах. Есть причины, по которым большинство (все?) Компиляторы C ++ реализуют виртуальные функции с помощью vtable, а не хранят полное определение функции для каждого экземпляра класса. Это не означает, что вы не получите увеличения производительности за счет сохранения всей функции в буфере, это просто не так просто, как может показаться.

12
ответ дан 18 December 2019 в 05:17
поделиться

Я думаю, вы можете оптимизировать что-то немного глупое. Кажется, вы надеетесь избежать дополнительных инструкций, разыменования функции или других подобных глупостей. Позвольте мне порекомендовать альтернативу. Сначала объявите свои функции как обычные, но пометьте их как inline : встроенный спецификатор указывает компилятору разместить тело функции при вызове функции, а не вызывать функцию обычным образом (если это возможно) .

inline int foo(int var)
{
  ...
}
inline int bar(int var)
{
  ...
}
inline int baz(int var)
{
  ...
}

Во-вторых, отправка этим функциям с помощью оператора switch . На большинстве аппаратных средств большинство компиляторов могут генерировать очень эффективный код, обычно таблицу переходов, или, если она более эффективна, тщательно продуманные инструкции перехода.

switch(somestruct.callcondition)
{
case FOO_CASE: 
    foo(...);
    break;
case BAR_CASE:
    bar(...);
    break;
case BAZ_CASE:
    baz(...);
    break;
}

Наконец, убедитесь, что ваш компилятор настроен на включение оптимизации . В режиме отладки большинство компиляторов не генерируют максимально эффективный код, потому что они пытаются сделать сгенерированный машинный код как можно более понятным, например, сохраняя порядок кода как можно ближе к источнику. Часто компилятор не выполняет встроенные вызовы, которые он мог бы, или не генерирует эффективные таблицы переходов, если вы специально этого не попросите.

тщательно составленные инструкции ветвления.

switch(somestruct.callcondition)
{
case FOO_CASE: 
    foo(...);
    break;
case BAR_CASE:
    bar(...);
    break;
case BAZ_CASE:
    baz(...);
    break;
}

Наконец, убедитесь, что ваш компилятор настроен на включение оптимизаций на . В режиме отладки большинство компиляторов не генерируют максимально эффективный код, потому что они пытаются сделать сгенерированный машинный код как можно более понятным, например, сохраняя порядок кода как можно ближе к источнику. Часто компилятор не выполняет встроенные вызовы, которые он мог бы, или не генерирует эффективные таблицы переходов, если вы специально этого не попросите.

тщательно составленные инструкции ветвления.

switch(somestruct.callcondition)
{
case FOO_CASE: 
    foo(...);
    break;
case BAR_CASE:
    bar(...);
    break;
case BAZ_CASE:
    baz(...);
    break;
}

Наконец, убедитесь, что ваш компилятор настроен на включение оптимизаций на . В режиме отладки большинство компиляторов не генерируют максимально эффективный код, потому что они пытаются сделать сгенерированный машинный код как можно более понятным, например, сохраняя порядок кода как можно ближе к источнику. Часто компилятор не выполняет встроенные вызовы, которые он мог бы, или не генерирует эффективные таблицы переходов, если вы специально этого не попросите.

2
ответ дан 18 December 2019 в 05:17
поделиться

Я знаю, что это закончилось неделю назад, но здесь все равно:

Во-первых, я не знаю, сработает ли это - но не могли бы вы просто написать макрос, похожий на вызов функции, который будет расширяться до встроенной инструкции перехода, чтобы буфер в соответствующей структуре? Это было бы похоже на переход к данным в структуре без вызова функции setup / teardown, но в коде это выглядело бы как вызов функции - но не было бы снижения производительности, потому что это будет обрабатываться препроцессором во время компиляции.

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

Не говоря уже о том, что использование буфера для хранения исполняемых данных - то же самое, что и указатель функции (поскольку буфер в любом случае является просто указателем на данные).

При этом C - довольно быстрый язык для начнем с - и компиляция с оптимизацией, вероятно, будет работать лучше, чем взлом на низком уровне (особенно с тем, что вы пытаетесь сделать). Прирост производительности (если он вообще есть) от попытки сохранить исполняемый код в буфере будет очень незначительным - и это только в тех случаях, когда он не работает медленнее. Такая производительность - более чем достаточно простой компромисс для удобочитаемости, переносимости и стабильности, которые вы получаете взамен.

Если вы пишете этот код для встраиваемой системы или чего-то еще, где производительность является такой щекотливой проблемой, вам следует подумать о написании этой части вашего приложения на ассемблере. Таким образом у вас будет больше контроля над поведением кода и вы сможете обойти соглашения о вызовах с помощью функций, внутренних по отношению к этой части. Вероятно, есть причина, по которой вы не можете делать что-то подобное в C: вам никогда не понадобится - и архитектура обычно не поддерживает это очень хорошо (если вообще поддерживает). Указатели на функции идеально подходят для желаемого здесь поведения, а функции-члены еще лучше. Если вы хотите стать еще лучше, вам придется написать сборку самостоятельно, иначе все развалится, и ее будет практически невозможно поддерживать.

добавление

теперь это еще старше , но у меня возникла другая мысль об этом: Вероятно, есть причина, по которой вы не можете делать что-то подобное в C: вам никогда не понадобится - и архитектура обычно не поддерживает это очень хорошо (если вообще поддерживает). Указатели на функции идеально подходят для желаемого здесь поведения, а функции-члены еще лучше. Если вы хотите стать еще лучше, вам придется написать сборку самостоятельно, иначе все развалится, и ее будет практически невозможно поддерживать.

добавление

теперь это еще старше , но у меня возникла другая мысль об этом: Вероятно, есть причина, по которой вы не можете делать что-то подобное в C: вам никогда не понадобится - и архитектура обычно не поддерживает это очень хорошо (если вообще поддерживает). Указатели на функции идеально подходят для желаемого здесь поведения, а функции-члены еще лучше. Если вы хотите стать еще лучше, вам придется написать сборку самостоятельно, иначе все развалится, и ее будет практически невозможно поддерживать.

добавление

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

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

1
ответ дан 18 December 2019 в 05:17
поделиться

Вот решение на C для сохранения функции в буфер:

#include <stdio.h>
#define FUNC_SIZE(func) ((long)(& func##_end)-(long)(&func))
#define FUNC_COPY(func,buf) {buf=malloc(FUNC_SIZE(func));memcpy(buf,&func,FUNC_SIZE(func));}

int magic(int arg){
  arg+=1;
  arg-=1;
  arg*=arg;
  /* WHATEVER */
  return arg;
}
void magic_end(){
  return;
}
int main(int argc, char *argv[])
{
  int (*buf)();
  printf("Size of the function: %i\n",FUNC_SIZE(magic));
  FUNC_COPY(magic,buf);
  printf("Result: %i, %i\n",(*buf)(7),magic(7));
  return 0;
}

Но я не думаю, что это быстрее, потому что вам все еще нужен указатель.

0
ответ дан 18 December 2019 в 05:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: