c - dlopen de la memoria?
dynamic-linking ld (4)
Estoy buscando una forma de cargar código de objeto generado directamente desde la memoria.
Entiendo que si lo escribo en un archivo, puedo llamar a dlopen para cargar dinámicamente sus símbolos y vincularlos. Sin embargo, esto parece un poco indirecto, teniendo en cuenta que comienza en la memoria, se escribe en el disco y luego se vuelve a cargar en la memoria por dlopen. Me pregunto si hay alguna manera de vincular dinámicamente el código objeto que existe en la memoria. Por lo que puedo decir, puede haber algunas maneras diferentes de hacer esto:
El truco consiste en pensar que la ubicación de tu memoria es un archivo, aunque nunca abandone la memoria.
Busque alguna otra llamada al sistema que haga lo que estoy buscando (no creo que esto exista).
Encuentre una biblioteca de enlaces dinámicos que pueda vincular el código directamente en la memoria. Obviamente, este es un poco difícil de buscar en Google, ya que la "biblioteca de enlaces dinámicos" ofrece información sobre cómo vincular bibliotecas dinámicamente, no en bibliotecas que realizan la tarea de vincular dinámicamente.
Resuma un API de un enlazador y cree una biblioteca nueva a partir de su base de código. (obviamente esta es la opción menos deseable para mí).
Entonces, ¿cuáles de estos son posibles? ¿factible? ¿Podrías indicarme alguna de las cosas que supuse que existían? ¿Hay alguna otra forma en la que ni siquiera he pensado?
Necesitaba una solución para esto porque tengo un sistema de secuencias de comandos que no tiene sistema de archivos (que usa blobs de una base de datos) y necesita cargar complementos binarios para admitir algunos scripts. Esta es la solución que se me ocurrió que funciona en FreeBSD pero que puede no ser portátil.
void *dlblob(const void *blob, size_t len) {
/* Create shared-memory file descriptor */
int fd = shm_open(SHM_ANON, O_RDWR, 0);
ftruncate(fd, len);
/* MemMap file descriptor, and load data */
void *mem = mmap(NULL, len, PROT_WRITE, MAP_SHARED, fd, 0);
memcpy(mem, blob, len);
munmap(mem, len);
/* Open Dynamic Library from SHM file descriptor */
void *so = fdlopen(fd,RTLD_LAZY);
close(fd);
return so;
}
Obviamente, el código carece de cualquier tipo de comprobación de errores, etc., pero esta es la funcionalidad principal.
ETA: Mi suposición inicial de que fdlopen
es POSIX estaba mal, esto parece ser un FreeBSD-ismo.
No necesita cargar el código generado en la memoria, ¡ya que está en la memoria!
Sin embargo, puede, de manera no portátil, generar código máquina en la memoria (siempre que esté en un segmento de memoria mmap -ed con indicador PROT_EXEC
).
(en ese caso, no se requiere un paso de "vinculación" o reubicación, ya que genera código de máquina con direcciones definitivas absolutas o relativas, en particular para llamar a funciones externas)
Existen algunas bibliotecas que hacen eso: en GNU / Linux bajo x86 o x86-64 , conozco GNU Lightning (que genera código de máquina rápidamente que se ejecuta lentamente), DotGNU LibJIT (que genera código de calidad media) y LLVM & GCCJIT (que es capaz de generar un código bastante optimizado en la memoria, pero toma tiempo para emitirlo). Y LuaJit tiene instalaciones similares. Desde 2015 GCC 5 tiene una biblioteca GCCJIT .
Y, por supuesto, aún puede generar código C en un archivo, bifurcar un compilador para compilarlo en un objeto compartido y dlopen ese archivo de objeto compartido. Estoy haciendo eso en GCC MELT , un lenguaje específico de dominio para extender GCC. Funciona bastante bien en la práctica.
adenda
Si el rendimiento de escribir el archivo C generado es una preocupación (no debería serlo, ya que compilar un archivo C es mucho más lento que escribirlo) considere usar algún sistema de archivos tmpfs para eso (quizás en /tmp/
que a menudo es un sistema de archivos tmpfs en Linux)
No veo por qué estarías considerando dlopen
, ya que requerirá mucho más código no portable para generar el formato de objeto correcto en el disco (por ejemplo, ELF) para la carga. Si ya sabe cómo generar código de máquina para su arquitectura, solo PROT_READ|PROT_WRITE|PROT_EXEC
memoria mmap
con PROT_READ|PROT_WRITE|PROT_EXEC
y coloque su código allí, luego asigne la dirección a un puntero de función y PROT_READ|PROT_WRITE|PROT_EXEC
. Muy simple.
No hay una forma estándar de hacerlo, aparte de escribir el archivo y luego cargarlo de nuevo con dlopen()
.
Puede encontrar algún método alternativo en su plataforma específica actual. Depende de usted decidir si eso es mejor que utilizar el enfoque ''estándar y (relativamente) portátil''.
Dado que la generación del código objeto en primer lugar es bastante específica de la plataforma, las técnicas adicionales específicas de la plataforma pueden no ser importantes para usted. Pero es un juicio crítico, y en todo caso depende de que haya una técnica no estándar, que es relativamente improbable.