Algo que me hizo bastante curioso fue que, dado que es posible en C ++ pasar una función como argumento en las circunstancias correctas, eso sugeriría que cualquier código interno que maneje esa función puede señalarse y escribirse y leerse en un binario como ejecutable. código.

Obviamente, esto proviene de alguien, aunque puedo tener una sólida formación en C ++, no estoy familiarizado con las intrincadas partes internas de cómo se gestiona la memoria en el montón y especialmente cómo el código de máquina ejecutable se ajusta a la imagen.

Supongo que dado que es posible pasar la referencia a una función, es posible obtener los datos señalados por ella y escribirla en algún lugar. No lo sé.

¿Alguien quiere decirme si esto es posible? Si es así, ¿puedes dar un ejemplo? Si no, por favor dime por qué. Me encanta aprender más en profundidad sobre cómo C ++ realmente funciona internamente.

-1
Kats 23 dic. 2016 a las 09:47

3 respuestas

La mejor respuesta

Hace 20 años, sus sugerencias podrían ser nuevas y útiles. La gente ahorraba memoria al cargar el código del archivo a pedido, luego lo llamaba y luego lo descargaba. Eso se llamaba superposiciones. Hasta cierto nivel es utilizable, pero en forma estandarizada en la plataforma y la API de la plataforma es lo que lo gestiona. El mecanismo detrás de las bibliotecas compartidas (.so en el sistema POSIX, .dll en Windows) es que el archivo de la biblioteca contiene etiquetas donde se encuentran ciertas funciones, cuál es su nombre, así como datos sobre cómo se deben inicializar la pila y el segmento de datos. El sistema puede hacerlo automáticamente cuando se carga el programa. De lo contrario, puede cargar la biblioteca manualmente y cargar el puntero para funcionar. P.ej. en Windows que sería por LoadLibrary () y GetProcAddress (), dlopen () y dlsym () en Linux.

Motivo por el que ahora no es posible en un lenguaje de alto nivel: seguridad, protección contra código malicioso en el segmento de datos. La biblioteca en tiempo de ejecución generalmente lo maneja. Todavía es posible usar el ensamblador, pero desafiará las medidas de seguridad antivirus y del sistema. Supongo que con una programación cuidadosa puede crear su propio "enlazador" para poder crear su propia biblioteca y cargar.

2
Swift - Friday Pie 23 dic. 2016 a las 07:42

No, realmente no puedes hacer esto. Hay muchas razones, pero aquí hay una simple e intuitiva: las funciones pueden llamar a otras funciones. Si pudiera escribir una función en el disco y restaurarla, esto no explicaría sus dependencias (funciones que llama, variables globales que actualiza, etc.). No va a funcionar

Si desea leer funciones desde el disco, es mejor expresarlas en un lenguaje de script como Lua. Esta es una solución probada que se utiliza en muchos productos comerciales, como los videojuegos y Adobe Lightroom.

1
John Zwinck 23 dic. 2016 a las 07:01

Si bien un puntero de función es el punto de entrada de una función, y esa memoria se puede leer y, por lo tanto, copiar, el primer problema es que no hay medios confiables para determinar la longitud de ese código, por lo que no puede determinar con certeza cómo mucho para copiar para obtener toda la función y solo eso.

La otra cuestión es ¿con qué fin práctico? Dependiendo de la plataforma, el código puede no ser reubicable y tendrá enlaces a otro código. El binario no contiene información simbólica; lo mejor que puede hacer es desmontarlo, pero fuera del contexto de todo el ejecutable vinculado, puede que no sea muy útil hacerlo.

Si su objetivo es separar las funciones del ejecutable principal y poder luego cargarlas y ejecutarlas, entonces para eso están las DLL y las bibliotecas compartidas.

Si solo desea observar el binario relacionado con una función, lo mejor es hacerlo en un depurador: tendrá un modo de vista de desmontaje que mostrará el código de ensamblaje binario sin formato (en hexadecimal) con enlaces simbólicos y la fuente C correspondiente . Esto tiene mucho más sentido si su objetivo es simplemente investigar cómo el código fuente se relaciona con el código binario de la máquina.

A continuación se muestra cómo podría hacer lo que está pidiendo, incluso si no hay una razón práctica para hacerlo. Hace suposiciones sobre el comportamiento del compilador que pueden no ser válidas en algunos casos. Supone, por ejemplo, que el compilador colocará funciones adyacentes contiguamente en la memoria y en el aumento de la dirección de la memoria, de modo que function2 esté inmediatamente después de function1 en la memoria. Aquí function2 sirve solo como un marcador final para function1 y puede ser ficticio.

int function1()
{
    ...

    return 0 ;
}

void function2() 
{
}

#include <stddef.h>

int main()
{
    ptrdiff_t function1_length = (char*)function2 - (char*)function1 ;

    FILE* fp = fopen( "function1.bin", "wb" ) ;
    fwrite( function1, function1_length, 1, fp ) ;
    fclose( fp ) ;
}
0
Clifford 23 dic. 2016 a las 08:25