Есть ли способ компилировать дополнительный код во время выполнения в C или C++?
Вот что я хочу сделать:
- Запустите программу и инициализируйте некоторые структуры данных.
- Затем скомпилируйте дополнительный код, который может получить доступ / изменить существующие структуры данных.
- Повторите шаг 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
Я думаю, что это будет работать для вас.
Есть одно простое решение:
- создать собственную библиотеку, имеющую специальные функции
- загрузить созданную библиотеку
- выполнять функции из этой библиотеки, передавать структуры как переменные функции
Чтобы использовать ваши структуры, вы должны включить те же заголовочные файлы, что и в главном приложении.
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) вызвать его через указатель на функцию или что-то еще
Конечно, есть и другой вариант - в зависимости от уровня взаимодействия, который вам нужен, вы можете создать отдельную программу и либо запустить ее и дождаться результата, либо отключить и запустить ее и поговорить с ней через каналы или сокеты. Если бы это соответствовало вашим потребностям, это было бы намного менее сложно.