• Lunes 6 de Mayo de 2024, 15:20

Autor Tema: [Artículo] Crear nuestra propia Run Time Library (Tercera Parte)  (Leído 3885 veces)

Eternal Idol

  • Moderador
  • ******
  • Mensajes: 4696
  • Nacionalidad: ar
    • Ver Perfil
[Artículo] Crear nuestra propia Run Time Library (Tercera Parte)
« en: Jueves 1 de Julio de 2004, 10:58 »
0
Crear nuestra propia Run Time Library (Tercera Parte)

En la MSDN encontré algo interesante sobre la opción /ENTRY del linker:


"Type a function name in the Entry-Point Symbol text box (or in the function argument on the command line). The function must be defined with the __stdcall calling convention. The parameters and return value must be defined as documented in the Win32 API for WinMain (for an .EXE file) or DllEntryPoint (for a DLL). It is recommended that you let the linker set the entry point so that the C run-time library is initialized correctly, and C++ constructors for static objects are executed."


Vamos a probar un poco de código con objetos estáticos:

ESTATICOS.CPP
Código: C++
  1. #include <windows.h>
  2. void SayLong(int number)
  3. {
  4.   char Data[10];
  5.   ltoa(number,Data,10);
  6.   MessageBox(0,Data,"El Numero",0);
  7. }
  8.  
  9. class Man
  10. {
  11.   public:
  12.   Man()
  13.   {
  14.     SayLong(66);
  15.   }
  16.   ~Man()
  17.   {
  18.     SayLong(99);
  19.   }
  20. };
  21.  
  22. Man Jacinto;
  23. Man Pedrito;
  24.  
  25. void main()
  26. {
  27.  
  28. }
  29.  

Compilando y linkeando este código con la Run Time Library por defecto conseguimos cuatro MessageBox, dos con 66 (el constructor) y dos con 99 (el destructor).
Ahora tratamos de compilar y linkear con nuestra Run Time Library y nos encontramos con lo siguiente:
estaticos.obj : error LNK2001: unresolved external symbol _atexit
estaticos.exe : fatal error LNK1120: 1 unresolved externals


Si vamos a la MSDN podemos ver que lo que hace esta función es ejecutar la función que le es pasada como parametro al final del programa. Usando una estructura de pila (LIFO).
Entonces que nos detiene? Que nos imposibilita crear una función simple como esa? Nada, manos a la obra señores.

C_ATEXIT.CPP
Código: C++
  1. #include <malloc.h>
  2. unsigned long *Funcs = 0;
  3. int n_funcs = 0;
  4.  
  5. extern "C" int atexit(void (__cdecl *func)(void))
  6. {
  7.   if (!Funcs) { Funcs = (unsigned long*)malloc(1); }
  8.   Funcs = (unsigned long*)realloc(Funcs,(n_funcs+1)*4);
  9.   Funcs[n_funcs] = (unsigned long)func;
  10.   n_funcs++;
  11.   return 0;
  12. }
  13.  
  14. extern "C" void destructores()
  15. {
  16.   for (int x = n_funcs-1;x >= 0;x--)
  17.   {
  18.     void (__cdecl *calla)(void) = (void (__cdecl *)(void))Funcs[x];
  19.     calla();
  20.   }
  21.   free(Funcs);
  22. }
  23.  

Hermoso el código verdad? Cada vez que se llama a la función atexit() se comprueba si ya tiene valor la estructura donde vamos a guardar los punteros a las funciones. Se realloca la memoria necesaria y se le asigna al elemento actual el parametro func. Siempre devolvemos cero que indica que no hubo ningún error.

Pero este código hermoso que hace exactamente hasta ahora? Ir llenando una lista de punteros a funciones, perfecto pero nos falta algo muy importante, recordemos que atexit() crea una lista de funciones que van a ser llamadas al final del programa. El principio del programa es la función de ENTRADA (solo nos interesan main, WinMain y DllMain ya que si fuera otra no usaría la Run Time Library, ni la nuestra ni la de VC++) que sabemos como se ejecuta, la llamamos nosotros desde nuestra Run Time Library por lo tanto sabemos perfectamente cuando termina de ejecutarse; en la siguiente instrucción a la llamada a main() el programa debe ejecutar la lista de funciones de atexit() y limpiar el buffer utilizado. Estas dos cosas las hace la función destructores() que está también en C_ATEXIT.CPP.

Ahora mismo podríamos compilar el programa ESTATICOS.CPP pero no pasaría nada, la compilación y el linkeo serían satisfactorios pero nuestro programa no haría absolutamente nada.
La pregunta del millón, quien llama a la función atexit(): la propia Run Time Library la llama. Acertaron? No me mientan eh.
Y cuando y porque la llama? Porque el compilador le pasa el puntero del destructor al ejecutar el constructor, por lo tanto, al no tener implementado nuestro código para manejar los constructores estáticos no recibimos ninguna llamada a atexit() y destructores() se ejecuta sin ningún puntero al que llamar.

Que hace el compilador para decirnos que existen constructores de objetos estáticos? El compilador crea un función diferente para cada uno de los objetos estáticos (esto lo podemos comprobar viendo las direcciones que le pasa por cada objeto a atexit) y nos pasa dentro del ejecutable las 'etiquetas' (posición en el archivo) de esas funciones a ejecutarse.


data_seg
#pragma data_seg( ["section-name"[, "section-class"] ] )

Specifies the default section for data. For example:

#pragma data_seg( "MY_DATA" )

causes data allocated following the #pragma statement to be placed in a section called MY_DATA.

Data allocated using the data_seg pragma does not retain any information about its location.


Estas secciones que se definen con #pragma data_seg son escritas por el linker dentro del ejecutable y tienen punteros, a inicializadores de C, constructores de C++, pre-terminadores de C y terminadores de C que forman tablas, estas se guardan en variables que tienen el comienzo y el fin de la tabla y son pasadas a una función llamada _initterm() que se encarga de llamar a todos esos punteros ejecutando su código. Esta es la forma en la que se llama a al constructor de de un objeto estático.

Nuestro nuevo C_MAIN.CPP:
Código: C++
  1. #include <malloc.h>
  2. #include <string.h>
  3.  
  4. extern "C" int __argc = 1;
  5. extern "C" char** __argv = 0;
  6.  
  7. extern "C" void __stdcall ExitProcess(unsigned long uExitCode);
  8. extern "C" char* __stdcall GetCommandLineA(void);
  9. extern int main(int argc, char *argv[],char *env[]);
  10.  
  11. extern "C" int __stdcall MessageBoxA(int a,char *b,char *c,unsigned int p);
  12.  
  13. typedef void (__cdecl *_PVFV)(void);
  14.  
  15. #pragma data_seg(".CRT$XIA")
  16. _PVFV __xi_a[] = { 0 };
  17.  
  18. #pragma data_seg(".CRT$XIZ")
  19. _PVFV __xi_z[] = { 0 };
  20.  
  21. #pragma data_seg(".CRT$XCA")
  22. _PVFV __xc_a[] = { 0 };
  23.  
  24. #pragma data_seg(".CRT$XCZ")
  25. _PVFV __xc_z[] = { 0 };
  26.  
  27. typedef void (* PFV)(void);
  28.  
  29. #pragma data_seg(".CRT$XPA")
  30. PFV __xp_a = 0;  /* C pre-terminators */
  31.  
  32. #pragma data_seg(".CRT$XPZ")
  33. PFV __xp_z = 0;
  34.  
  35. #pragma data_seg(".CRT$XTA")
  36. PFV __xt_a = 0;   /* C terminators */
  37.  
  38. #pragma data_seg(".CRT$XTZ")
  39. PFV __xt_z = 0;
  40.  
  41. extern "C" void __cdecl _initterm(_PVFV *, _PVFV *);
  42.  
  43. //__xi_a[], __xi_z[];    /* C initializers */
  44. //__xc_a[], __xc_z[];    /* C++ initializers */
  45. //__xp_a[], __xp_z[];    /* C pre-terminators */
  46. //__xt_a[], __xt_z[];    /* C terminators */
  47.  
  48. extern "C" void destructores();
  49.  
  50. #define FALSE 0
  51. #define TRUE 1
  52.  
  53. extern "C" void mainCRTStartup()
  54. {
  55.   char *parametros = GetCommandLineA();
  56.   char *temp = (char*)malloc(2048);
  57.   memset(temp,0,2048);
  58.   __argc = 0;
  59.   __argv = (char**)malloc(4);
  60.   char Except = FALSE;
  61.   while(*parametros)
  62.   {
  63.     if (*parametros == 34)
  64.     {
  65.       if (Except == FALSE)
  66.       {
  67.         Except = TRUE;
  68.       }
  69.       else
  70.       {
  71.         Except = FALSE;
  72.         if (strlen(temp) > 0)
  73.         {
  74.           __argv = (char**)realloc(__argv,4 * (__argc+1));
  75.           __argv[__argc] = (char*)malloc(strlen(temp) + 1);
  76.           memset(__argv[__argc],0,strlen(temp) + 1);
  77.           strcpy(__argv[__argc],temp);
  78.           strcpy(temp,"");
  79.           __argc++;
  80.         }
  81.       }
  82.       parametros++;
  83.       continue;
  84.     }
  85.    
  86.     if ( (*parametros == 32) && (Except == FALSE) )
  87.     {
  88.       if (strlen(temp) > 0)
  89.       {
  90.         __argv = (char**)realloc(__argv,4 * (__argc+1));
  91.         __argv[__argc] = (char*)malloc(strlen(temp) + 1);
  92.         memset(__argv[__argc],0,strlen(temp) + 1);
  93.         strcpy(__argv[__argc],temp);
  94.         strcpy(temp,"");
  95.         __argc++;
  96.       }
  97.     }
  98.     else
  99.     {
  100.       unsigned long pos = strlen(temp);
  101.       temp[pos] = *parametros;
  102.       temp[pos+1] = 0;
  103.     }
  104.     parametros++;
  105.   }
  106.   if (strlen(temp) > 0)
  107.   {
  108.     __argv = (char**)realloc(__argv,4 * (__argc+1));
  109.     __argv[__argc] = (char*)malloc(strlen(temp) + 1);
  110.     memset(__argv[__argc],0,strlen(temp) + 1);
  111.     strcpy(__argv[__argc],temp);
  112.     strcpy(temp,"");
  113.     __argc++;
  114.   }
  115.   free(temp);
  116.   _initterm(__xi_a,__xi_z);
  117.   _initterm(__xc_a,__xc_z);
  118.   main(__argc,__argv,0);
  119.   _initterm(&__xp_a,&__xp_z);
  120.   _initterm(&__xt_a,&__xt_z);
  121.   destructores();
  122.  
  123.   for (int y = 0;y < __argc;y++) { free(__argv[y]); }
  124.   free(__argv);
  125.   ExitProcess(0);
  126. }
  127.  
  128. static void __cdecl _initterm(_PVFV *pfbegin,_PVFV *pfend)
  129. {
  130.   while (pfbegin < pfend)
  131.   {
  132.     if (*pfbegin) { (**pfbegin)(); }
  133.     ++pfbegin;
  134.   }
  135. }
  136.  

Este nuevo main define las diferentes secciones donde se encontraran las benditas tablas, llama a las tablas de inicio justo antes que a main() y a las de terminaciones justo después, además de llamar a nuestra función destructores(). Las tablas son procesadas por la función initterm() que recorre la tablae de punteros a funciones, ejecutando cada uno de ellas, hasta encontrar un puntero nulo.

Creamos nuestra libreria y ESTATICOS.EXE:

cl /c *.cpp
lib *.obj /out:clib.lib
cl /c ESTATICOS.CPP
link ESTATICOS.OBJ /NODEFAULTLIB USER32.LIB KERNEL32.LIB CLIBCLIB.LIB


Ahora si, tenemos el mismo comportamiento que con la Run Time Library de VC++ para los objetos estáticos.

Un regalito en esta parte, vamos a ver como manejar las variables de entorno:

C_MAIN.CPP
Código: C++
  1. #include <malloc.h>
  2. #include <string.h>
  3.  
  4. //entorno
  5. extern "C" int vars = 0;
  6. extern "C" char** _environ = 0;
  7. extern "C" char* __stdcall GetEnvironmentStrings(void);
  8. extern "C" bool __stdcall FreeEnvironmentStringsA(char *block);
  9. void __cdecl _envinit(void);
  10.  
  11. //parametros
  12. extern "C" int __argc = 1;
  13. extern "C" char** __argv = 0;
  14.  
  15. extern "C" void __stdcall ExitProcess(unsigned long uExitCode);
  16. extern "C" char* __stdcall GetCommandLineA(void);
  17. extern int main(int argc, char *argv[],char *env[]);
  18.  
  19. extern "C" int __stdcall MessageBoxA(int a,char *b,char *c,unsigned int p);
  20.  
  21. typedef void (__cdecl *_PVFV)(void);
  22.  
  23. #pragma data_seg(".CRT$XIA")
  24. _PVFV __xi_a[] = { 0 };
  25.  
  26. #pragma data_seg(".CRT$XIZ")
  27. _PVFV __xi_z[] = { 0 };
  28.  
  29. #pragma data_seg(".CRT$XCA")
  30. _PVFV __xc_a[] = { 0 };
  31.  
  32. #pragma data_seg(".CRT$XCZ")
  33. _PVFV __xc_z[] = { 0 };
  34.  
  35. typedef void (* PFV)(void);
  36.  
  37. #pragma data_seg(".CRT$XPA")
  38. PFV __xp_a = 0;  /* C pre-terminators */
  39.  
  40. #pragma data_seg(".CRT$XPZ")
  41. PFV __xp_z = 0;
  42.  
  43. #pragma data_seg(".CRT$XTA")
  44. PFV __xt_a = 0;   /* C terminators */
  45.  
  46. #pragma data_seg(".CRT$XTZ")
  47. PFV __xt_z = 0;
  48.  
  49. extern "C" void __cdecl _initterm(_PVFV *, _PVFV *);
  50.  
  51. //__xi_a[], __xi_z[];    /* C initializers */
  52. //__xc_a[], __xc_z[];    /* C++ initializers */
  53. //__xp_a[], __xp_z[];    /* C pre-terminators */
  54. //__xt_a[], __xt_z[];    /* C terminators */
  55.  
  56. extern "C" void destructores();
  57.  
  58. #define FALSE 0
  59. #define TRUE 1
  60.  
  61. extern "C" void mainCRTStartup()
  62. {
  63.   char *parametros = GetCommandLineA();
  64.   char *temp = (char*)malloc(2048);
  65.   memset(temp,0,2048);
  66.   __argc = 0;
  67.   __argv = (char**)malloc(4);
  68.   char Except = FALSE;
  69.   while(*parametros)
  70.   {
  71.     if (*parametros == 34)
  72.     {
  73.       if (Except == FALSE)
  74.       {
  75.         Except = TRUE;
  76.       }
  77.       else
  78.       {
  79.         Except = FALSE;
  80.         if (strlen(temp) > 0)
  81.         {
  82.           __argv = (char**)realloc(__argv,4 * (__argc+1));
  83.           __argv[__argc] = (char*)malloc(strlen(temp) + 1);
  84.           memset(__argv[__argc],0,strlen(temp) + 1);
  85.           strcpy(__argv[__argc],temp);
  86.           strcpy(temp,"");
  87.           __argc++;
  88.         }
  89.       }
  90.       parametros++;
  91.       continue;
  92.     }
  93.    
  94.     if ( (*parametros == 32) && (Except == FALSE) )
  95.     {
  96.       if (strlen(temp) > 0)
  97.       {
  98.         __argv = (char**)realloc(__argv,4 * (__argc+1));
  99.         __argv[__argc] = (char*)malloc(strlen(temp) + 1);
  100.         memset(__argv[__argc],0,strlen(temp) + 1);
  101.         strcpy(__argv[__argc],temp);
  102.         strcpy(temp,"");
  103.         __argc++;
  104.       }
  105.     }
  106.     else
  107.     {
  108.       unsigned long pos = strlen(temp);
  109.       temp[pos] = *parametros;
  110.       temp[pos+1] = 0;
  111.     }
  112.     parametros++;
  113.   }
  114.   if (strlen(temp) > 0)
  115.   {
  116.     __argv = (char**)realloc(__argv,4 * (__argc+1));
  117.     __argv[__argc] = (char*)malloc(strlen(temp) + 1);
  118.     memset(__argv[__argc],0,strlen(temp) + 1);
  119.     strcpy(__argv[__argc],temp);
  120.     strcpy(temp,"");
  121.     __argc++;
  122.   }
  123.   free(temp);
  124.   _initterm(__xi_a,__xi_z);
  125.   _initterm(__xc_a,__xc_z);
  126.   _envinit();
  127.   main(__argc,__argv,_environ);
  128.   _initterm(&__xp_a,&__xp_z);
  129.   _initterm(&__xt_a,&__xt_z);
  130.   destructores();
  131.  
  132.   for (int y = 0;y < __argc;y++) { free(__argv[y]); }
  133.   free(__argv);
  134.   for (int z = 0;z < vars;z++)
  135.   {
  136.     if (_environ[z]) { free(_environ[z]); }
  137.   }  
  138.   free(_environ);
  139.   ExitProcess(0);
  140. }
  141.  
  142. static void __cdecl _initterm(_PVFV *pfbegin,_PVFV *pfend)
  143. {
  144.   while (pfbegin < pfend)
  145.   {
  146.     if (*pfbegin) { (**pfbegin)(); }
  147.     ++pfbegin;
  148.   }
  149. }
  150.  
  151. void __cdecl _envinit(void)
  152. {
  153.   char *var = GetEnvironmentStrings();
  154.   _environ = (char**)malloc(4);
  155.   while(*var)
  156.   {
  157.     _environ = (char**)realloc(_environ,(vars + 1) * 4);
  158.     _environ[vars] = (char*)malloc(strlen(var) + 1);
  159.     memset(_environ[vars],0,strlen(var) + 1);
  160.     strcpy(_environ[vars],var);    
  161.     (char*)var += strlen((char*)var);
  162.     (char*)var +=1;
  163.     vars++;
  164.   }
  165.   FreeEnvironmentStringsA(var);
  166. }
  167.  

Esta nueva versión del C_MAIN.CPP incorpora la función _envinit, que cre la lista de variables de entorno y le pasa la variable _environ a la función main() de nuestro programa.

Ahora vamos a implementar getenv y putenv:

C_PUTENV.CPP
Código: C++
  1. #include <malloc.h>
  2. #include <string.h>
  3.  
  4. extern "C" int vars;
  5. extern "C" char** _environ;
  6.  
  7. bool compararPut(char *s1,const char *s2)
  8. {
  9.   while(*s2)
  10.   {
  11.     if (*s1 != *s2)
  12.     {
  13.       return false;
  14.     }
  15.     s1++;
  16.     s2++;
  17.   }
  18.   return true;
  19. }
  20.  
  21. extern "C" int putenv(const char *envstring)
  22. {
  23.   for (int x = 0;x < vars;x++)
  24.   {
  25.     if (!_environ[x]) { continue; }
  26.     if (compararPut(_environ[x],envstring) == true)
  27.     {
  28.       char *ptr = (char*)envstring;
  29.       while(*envstring)
  30.       {
  31.         if ( (*ptr == '=') && (*++ptr == 0) )
  32.         {
  33.           free(_environ[x]);
  34.           _environ[x] = 0;
  35.           return 0;  
  36.         }    
  37.         ptr++;    
  38.       }      
  39.       strcpy(_environ[x],envstring);
  40.       return 0;
  41.     }
  42.   }
  43.   _environ = (char**)realloc(_environ,(vars + 1) * 4);
  44.   _environ[vars] = (char*)malloc(strlen(envstring) + 1);
  45.   memset(_environ[vars],0,strlen(envstring) + 1);
  46.   strcpy(_environ[vars],envstring);    
  47.   vars++;
  48.   return 0;
  49. }
  50.  

C_GETENV.CPP
Código: C++
  1. #include <string.h>
  2.  
  3. extern "C" int vars;
  4. extern "C" char** _environ;
  5.  
  6. bool compararGet(char *s1,const char *s2)
  7. {
  8.   while(*s2)
  9.   {
  10.     if (*s1 != *s2)
  11.     {
  12.       return false;
  13.     }
  14.     s1++;
  15.     s2++;
  16.   }
  17.   if (*s1 == '=') { return true; }
  18.   return false;
  19. }
  20.  
  21. extern "C" char *getenv(const char *varname)
  22. {
  23.   for (int x = 0;x < vars;x++)
  24.   {
  25.     if (!_environ[x]) { continue; }
  26.     if (compararGet(_environ[x],varname) == true)
  27.     {
  28.       return (_environ[x]+strlen(varname)+1);
  29.     }
  30.   }
  31.   return 0;
  32. }
  33.  

Podemos probar su correcto funcionamiento con cualquier programa que use el parametro envp o simplemente cambiando variables del entorno como TMP (el path de los archivos temporales).

En la cuarta parte veremos como implementar las funciones de entrada y salida por consola.

Saludos,
Mariano.

Autor: Mariano Ventaja

http://www.c0d3rz.com.ar

Descarga: http://c0d3rz.com.ar/foro/viewtopic.php?t=104

Nacional y Popular En mi país la bandera de Eva es inmortal.


Queremos una Argentina socialmente justa, económicamente libre y  políticamente soberana.
¡Perón cumple, Evita dignifica!


La mano invisible del mercado me robo la billetera.