Есть ли способ компилировать дополнительный код во время выполнения в C или C++?

Вот что я хочу сделать:

  1. Запустите программу и инициализируйте некоторые структуры данных.
  2. Затем скомпилируйте дополнительный код, который может получить доступ / изменить существующие структуры данных.
  3. Повторите шаг 2 по мере необходимости.

Я хочу быть в состоянии сделать это с обоими C а также C++ с помощью gcc (и в конце концов Java) в Unix-подобных системах (особенно в Linux и Mac OS X). Идея состоит в том, чтобы в основном реализовать цикл read-eval-print для этих языков, который компилирует выражения и операторы по мере их ввода и использует их для изменения существующих структур данных (что постоянно делается на языках сценариев). Я пишу этот инструмент в python, который генерирует C/C++ файлы, но это не должно быть актуальным.

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

Я бы предпочел не использовать какие-либо компиляторы, кроме gcc если нет абсолютно никакого способа сделать это в gcc,

Если у кого-то есть какие-либо идеи или они знают, как это сделать, любая помощь будет принята с благодарностью.

6 ответов

Решение

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

void * lib = dlopen("mynewcode.so", RTLD_LAZY);
if(lib) {
    void (*fn)(void) = dlsym(lib, "libfunc");

    if(fn) fn();
    dlclose(lib);
}

Очевидно, вам придется компилировать новый код по мере продвижения, но если вы продолжите заменять mynewcode.so Я думаю, что это будет работать для вас.

Есть одно простое решение:

  1. создать собственную библиотеку, имеющую специальные функции
  2. загрузить созданную библиотеку
  3. выполнять функции из этой библиотеки, передавать структуры как переменные функции

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

structs.h:

struct S {
    int a,b;
};

main.cpp:

#include <iostream>
#include <fstream>
#include <dlfcn.h>
#include <stdlib.h>

#include "structs.h"

using namespace std;

int main ( int argc, char **argv ) {

    // create own program
    ofstream f ( "tmp.cpp" );
    f << "#include<stdlib.h>\n#include \"structs.h\"\n extern \"C\" void F(S &s) { s.a += s.a; s.b *= s.b; }\n";
    f.close();

    // create library
    system ( "/usr/bin/gcc -shared tmp.cpp -o libtmp.so" );

    // load library        
    void * fLib = dlopen ( "./libtmp.so", RTLD_LAZY );
    if ( !fLib ) {
        cerr << "Cannot open library: " << dlerror() << '\n';
    }

    if ( fLib ) {
        int ( *fn ) ( S & ) = dlsym ( fLib, "F" );

        if ( fn ) {
            for(int i=0;i<11;i++) {
                S s;
                s.a = i;
                s.b = i;

                // use function
                fn(s);
                cout << s.a << " " << s.b << endl;
            }
        }
        dlclose ( fLib );
    }

    return 0;
}

выход:

0 0
2 1
4 4
6 9
8 16
10 25
12 36
14 49
16 64
18 81
20 100

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

Несмотря на то, что LLVM сегодня используется в основном для его оптимизации и внутренних функций при компиляции, его ядром является низкоуровневая виртуальная машина.

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

Однако C и C++ не очень дружелюбны для такого рода вещей.

Да, вы можете сделать это с помощью Runtime Compiled C++ (или взглянуть на блог и видеоролики по RCC++) или одной из альтернатив.

Это можно сделать с помощью OpenCL.

OpenCL - широко поддерживаемый стандарт, в основном используемый для разгрузки вычислений на специализированное оборудование, такое как графические процессоры. Тем не менее, он также отлично работает на процессорах и фактически выполняет компиляцию кода, подобного C99, во время выполнения, как одну из его основных функций (именно так достигается переносимость аппаратного обеспечения). Более новые версии (2.1+) также принимают большое подмножество C++14.

Базовый пример такой компиляции и выполнения во время выполнения может выглядеть примерно так:

#ifdef __APPLE__
#include<OpenCL/opencl.h>
#else
#include<CL/cl.h>
#endif
#include<stdlib.h>
int main(int argc,char**argv){//assumes source code strings are in argv
    cl_int e = 0;//error status indicator
    cl_platform_id platform = 0;
    cl_device_id device = 0;
    e=clGetPlatformIDs(1,&platform,0);                                      if(e)exit(e);
    e=clGetDeviceIDs(platform,CL_DEVICE_TYPE_ALL,1,&device,0);              if(e)exit(e);
    cl_context context = clCreateContext(0,1,&device,0,0,&e);               if(e)exit(e);
    cl_command_queue queue = clCreateCommandQueue(context,device,0,&e);     if(e)exit(e);
    //the lines below could be done in a loop, assuming you release each program & kernel
    cl_program program = clCreateProgramWithSource(context,argc,(const char**)argv,0,&e);
    cl_kernel kernel = 0;                                                   if(e)exit(e);
    e=clBuildProgram(program,1,&device,0,0,0);                              if(e)exit(e);
    e=clCreateKernelsInProgram(program,1,&kernel,0);                        if(e)exit(e);
    e=clSetKernelArg(kernel,0,sizeof(int),&argc);                           if(e)exit(e);
    e=clEnqueueTask(queue,kernel,0,0,0);                                    if(e)exit(e);
    //realistically, you'd also need some buffer operations around here to do useful work
}

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

1) использовать system() или что-то еще для выполнения gcc или make или что-то для сборки кода

2) либо связать его как простой двоичный файл, либо проанализировать любой формат (elf?), Который выводит компоновщик на вашей платформе самостоятельно

3) получите себе несколько исполняемых страниц, либо с помощью mmap () запустите исполняемый файл, либо выполните анонимный mmap с установленным битом выполнения и скопируйте / распакуйте там свой код (не все платформы заботятся об этом бите, но давайте предположим, что у вас есть тот, который делает)

4) очистить любые кэши данных и инструкций (поскольку согласованность между ними обычно не гарантируется)

5) вызвать его через указатель на функцию или что-то еще

Конечно, есть и другой вариант - в зависимости от уровня взаимодействия, который вам нужен, вы можете создать отдельную программу и либо запустить ее и дождаться результата, либо отключить и запустить ее и поговорить с ней через каналы или сокеты. Если бы это соответствовало вашим потребностям, это было бы намного менее сложно.

Другие вопросы по тегам