Ir al contenido



Foto

Mini ejecutables en C/C++ para Windows


  • Por favor identifícate para responder
9 respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.831 mensajes
  • LocationMadrid - España

Escrito 21 mayo 2009 - 05:56

Las aplicaciones creadas de forma estándar con el Builder ocupan demasiado espacio cuando pretendemos una aplicación de consola sin VCL sólo con API. El problema es similar, de alguna forma, a lo que ocurre con el delphi y que en alguna ocasión comentó cHackAll.

Las aplicaciones que usan sólo la APIs pueden minimizarse en tamaño enormemente. Si una aplicación mínima en Builder, tipo consola, pesa 46k, podemos dejarla en 4k. Si posteriormente la comprimimos podrá ocupar 1k.

La forma de conseguir este objetivo es minimizar las librerías y módulos .obj que se enlazan al ejecutable.

Una aplicación normal presenta en el archivo de proyecto la siguiente fracción:
 

delphi
  1. <LINKER>
  2.   <ALLOBJ value="c0w32.obj sysinit.obj $(OBJFILES)"/>
  3.   <ALLRES value="$(RESFILES)"/>
  4.   <ALLLIB value="$(LIBFILES) $(LIBRARIES) import32.lib cp32mt.lib"/>
  5. </LINKER>

Como se ve, se enlazan c0w32.obj, sysinit.obj y las librerías import32.lib cp32mt.lib. Se puede prescindir de todos los archivos excepto de import32.lib.
c0w32.obj  - 2.302 bytes
sysinit.obj - 3.113 bytes
cp32mt.lib  - 1.727.488 bytes (no se enlazan todas las funciones)
c0w32.obj se encarga de inicializar las librerías del Builder. Se enlazan partes de las librerías RTL que se incluyen en el ejecutable, aunque luego el programa no haga uso de ellas.

Si evitamos que se enlacen los módulos que no nos hacen falta conseguiremos una reducción considerable en el peso final del programa:
 

delphi
  1. <LINKER>
  2.   <ALLOBJ value="$(OBJFILES)"/>
  3.   <ALLRES value="$(RESFILES)"/>
  4.   <ALLLIB value="$(LIBFILES) $(LIBRARIES) import32.lib"/>
  5. </LINKER>

Pero la cosa no es tan sencilla, habrá que hacer algún cambio mas. Tendremos que reescribir la entrada a la aplicación:

MAIN.ASM:

asm
  1. .586p
  2. Locals
  3. jumps
  4. .model flat, stdcall
  5.  
  6. extrn  _main    : PROC
  7.  
  8. .data
  9. .code
  10.  
  11. Entry:
  12.   JMP _main
  13.  
  14. End    Entry

La anterior rutina inicia el programa y llama a la función C main(), que se encargará de preparar los parámetros para llamar a la clásica función WinMain, esta ya conocida y familiar.

_MAIN.CPP:

cpp
  1. #include <windows.h>
  2.  
  3. // Punto de entrada al programa
  4. // !! PREVIO a WinMain !!
  5.  
  6. extern "C" void Main()
  7. {
  8.   STARTUPINFO  info;
  9.   HINSTANCE    hinstance;
  10.   TCHAR        *CmdLine;
  11.   GetStartupInfo( &info);
  12.   hinstance = GetModuleHandle(NULL);
  13.   CmdLine = GetCommandLine();
  14.   int n = 0;
  15.   if(*CmdLine==char(34))
  16.     while(CmdLine[++n]!=34);
  17.   while(CmdLine[n++]!=char(32));
  18.   CmdLine = n==lstrlen(CmdLine) ? 0 : CmdLine+n;
  19.   ExitProcess(WinMain(hinstance, NULL, CmdLine, info.wShowWindow));
  20. }

Ahora sólo queda escribir la función WinMain:

WinMain.cpp:

cpp
  1. //---------------------------------------------------------------------------
  2.  
  3. #include <windows.h>
  4. #pragma hdrstop
  5.  
  6. //---------------------------------------------------------------------------
  7.  
  8. #pragma argsused
  9.  
  10.  
  11. WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  12. {
  13.   MessageBox(0, "Hola, Mundo!!", "MiniAPP", MB_OK);
  14.   return 0;
  15. }
  16. //---------------------------------------------------------------------------

El resultado es un ejecutable que ocupa 4k (4.096 bytes) sin compresión alguna. Si usamos compresores de código podremos alcanzar 1k.

Podemos crear también aplicaciones con ventanas usando sólo la API de Windows.
Sin embargo este sistema tiene un pequeño inconveniente, no funcionan algunas funciones de las C/C++ estándar como las de manejo de memoria y cadenas, que no estén implementadas en la API. Por ejemplo, no funciona el operador new ni la función strlen. En este último caso podremos usar lstrlen que es el equivalente que tiene Windows. Para manejar la memoria podemos usar VirtualAlloc que nos permitirá reservar memoria sin dificultad.

En definitiva, es un método de ahorrar tamaño en los ejecutables pequeños, que perderá su sentido en aplicaciones mayores en las que el uso amplio e intensivo de la RTL la hará rentable desde el punto de vista del espacio.

Adjunto los archivos descritos y el de proyecto para Builder5 con dos versiones del ejecutable, una de 4.096 bytes y otra, comprimida, de 929 bytes.

Espero que esta información sea de utilidad y de aclaración del funcionamiento interno del Builder.

Saludos.


Edito para resubir archivo adjunto corrupto y reparar caracteres dañados en el texto

Archivos adjuntos


  • 0

#2 cHackAll

cHackAll

    Advanced Member

  • Administrador
  • 598 mensajes

Escrito 21 mayo 2009 - 02:58

...El problema es similar, de alguna forma, a lo que ocurre con el delphi y que en alguna ocasión comentó cHackAll...


...obtenemos una SysInit.pas al 5% de la original y una System.pas al 0.8%. Con el afán de seguir optimizando nuestro ejecutable debemos mutilar los "relocations" y recursos innecesarios...

"Obteniendo ejecutables ligeros" (enlace Platino)

...Adjunto los archivos descritos y el de proyecto para Builder5 con dos versiones del ejecutable, una de 4.096 bytes y otra, comprimida, de 929 bytes...


El enlace a mi articulo es Platino porque contiene cierta informacion que puede ser polemica. Tambien es importante aclarar que _MiniApp.exe (adjunto) por escafandra NO contiene codigo malicioso;

...[off-topic]MEW es un controversial compresor, detectado como posible alimaña por un buen numero de Antivirus, sin embargo en mi humilde opinión este es un echo dado solo por...[/off-topic]...


 

He visto que el BCB utiliza las unidades compiladas de Delphi (DCU) creando cabezales (HPP) y siempre pense que las aplicaciones de BCB de fondo utilizaban a éstas en la VCL; esto significarí­a que con SysInit.hpp y System.hpp no sería necesario los .OBJ a los que haces referencia en el primer post del hilo. Ahora me pregunto a quien transfiere el control al momento de compilar; a c0w32.obj ó a sysinit.obj?

En fin, ahora que veo tu articulo escafandra puedo notar ciertas diferencias entre Delphi y BCB al realizar Mini ejecutables, concluyo que en ninguno de los casos es sencillo, pero entre ambos, lo mejor es utilizar BCB o simplemente C.

Salud! (y)

Edito para reparar caracteres dañados en el texto
  • 0

#3 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.831 mensajes
  • LocationMadrid - España

Escrito 21 mayo 2009 - 05:20

El enlace a mi articulo es Platino porque contiene cierta información que puede ser polémica. También es importante aclarar que _MiniApp.exe (adjunto) por escafandra NO contiene código malicioso;

Traté de pasar de puntillas sobre la polémica de los compresores, pero veo que es mejor dejarlo claro. El adjunto _MiniApp.exe está comprimido con NEW y puede parecer sospechoso a algunos sistemas de seguridad, pero ciertamente no contiene código malicioso alguno. El otro adjunto, MiniApp.exe no tiene ningún tipo de compresión. Aprovecho para decir que NEW es el compresor con el que mejores resultados he obtenido de los que he probado.

En fin, ahora que veo tu articulo escafandra puedo notar ciertas diferencias entre Delphi y BCB al realizar Mini ejecutables, concluyo que en ninguno de los casos es sencillo, pero entre ambos, lo mejor es utilizar BCB o simplemente C.


Ciertamente no es sencillo. Para el caso del Builder, amputamos demasiado funciones del estándar. He hecho alguna prueba compilando algunas librerías del C estándar que trae el Builder y con unos pocos Kb mas se puede solucionar. Bien es verdad que con un buen uso de la API no son necesarias. De momento no deja de ser un experimento.

El el caso del Visual C, ocurre algo similar al Builder, Se debe crear un nuevo punto de entrada y eliminar las librerías de la RTL. Para el que esté interesado, le recomiendo este interesante enlace.

No se si será mejor usar delphi o Builder para este asunto. Delphi consigue ejecutables muy pequeños y mas bien depende de los hábitos de cada uno. En fin, tus comentarios siempre son bienvenidos, cHackAll. :)

Saludos.

Edito para reparar caracteres dañados en el texto
  • 0

#4 cHackAll

cHackAll

    Advanced Member

  • Administrador
  • 598 mensajes

Escrito 23 mayo 2009 - 09:30

...El el caso del Visual C, ocurre algo similar al Builder, Se debe crear un nuevo punto de entrada y eliminar las librerías de la RTL...


Veamos que sucede con Visual Studio 2008 express; primero creamos un nuevo proyecto de tipo consola en C++

Imagen Enviada

Aceptamos y en lugar de finalizar el Wizard para creación de nuevos proyectos continuamos; entonces cambiamos el tipo de proyecto a Ventanas (para que al iniciar el proceso nuevo no sea tratado como una consola), y marcamos la opción de "proyecto vací­o" para no utilizar ninguna plantilla;

Imagen Enviada

Una vez con el proyecto nuevo vací­o, le hacemos clic derecho añadir un nuevo item .CPP en el que habremos de escribir nuestro código, pero antes de hacerlo modificaremos las propiedades del proyecto (no de la solución);

Imagen Enviada

Lo primero que necesitaremos es definir un punto de entrada, esto lo hacemos seleccionando en el árbol de la parte izquierda Linker > Advanced; a continuación revisamos los siguientes valores;

Entry Point: main (el punto de entrada del programa)
No Entry Point: No (doble negación...)
Base Address: 0x00400000 (vamos a utilizar una base fija para que el compilador no genere relocations)
Randomized Base Address: Disable Image Randomization
Fixed Base Address: Image must be loaded at a fixed address (aquí definimos explí­citamente que utilice la base definida)

Imagen Enviada

Habiendo definido dichos valores ahora podemos escribir un pequeño código;

Imagen Enviada

Tenemos un Mini Hello World de 21 Kb. reduzcamos 1 Kb. eliminando cualquier información de depuración (Generate Debug Info: No);

Imagen Enviada

Ahora modifiquemos el tipo de optimización de compilado para que genere un opcode ligero (Optimization: Minimize Size);

Imagen Enviada

Por compatibilidad debemos definir por defecto (anular) la optimización para la generación de código en tiempo de ejecución [en otras palabras el opcode generado dependerá de la dimensión del tipo de dato y no habrá opcode adicional que verifique errores en tiempo de ejecución] (Basic runtime Checks: Default, Enable Minimal Rebuild: No)

Imagen Enviada

Anulamos la información de depuración generada (Debug Information Format: Disabled);

Imagen Enviada

Habiendo reducido el ejecutable a 10 Kb. lo reducimos a 3 Kb. eliminando un pseudos-cache de generado de funciones en el binario, el cual deja literalmente espacios vacíos para contener nuevas funciones escritas entre compilaciones (Enable Incremental Linking: No);

Imagen Enviada

Finalmente y si no es necesario utilizar el manifiesto para Vista+ (o especiales) eliminamos dicho recurso (Generate Manifest: No);

Imagen Enviada

Con esto conseguimos un ejecutable de 2 Kb.,  sin compresión y sin tener conocimientos especializados (ver archivo adjunto)

Salud!

Edito para reparar caracteres dañados en el texto

Archivos adjuntos


  • 0

#5 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.831 mensajes
  • LocationMadrid - España

Escrito 24 mayo 2009 - 05:26

Al final has escrito todo un tutoral para miniejecutables en Visual C  :D  (y)

Posiblemente el compilador de Microsoft sea mejor que el Builder en cuanto opciones de compilación y enlace. Por desgracia no puedo compilar ni jugar con tu ejemplo porque no dispongo del Visual Studio 2008. Siempre he pensado que para una sola API el tamaño que ronda 4K es grande. Pero posiblemente el Visual C tenga una ventaja respecto a otros compiladores. Esa ventaja pudiera venir de no tener que distribuir librerías que vienen de serie en el propio S.O. y ayudarle a minimizar los ejecutables. Me gustaría poder probar tu ejemplo y comprobar, como creo, que no se amputan las funciones de las librerías estándar de C/C+ con tu sistema.

Muy buen ejemplo, amigo.

Saludos.

Edito para reparar caracteres dañados en el texto
  • 0

#6 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 13.936 mensajes
  • LocationMéxico

Escrito 24 mayo 2009 - 09:14

No seria mala idea colocar este tutorial en el foro correspondente, que opinan?

Salud OS
  • 0

#7 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.831 mensajes
  • LocationMadrid - España

Escrito 24 mayo 2009 - 09:42

No seria mala idea colocar este tutorial en el foro correspondente, que opinan?

Salud OS


La cosa comenzó como un artículo o nota informativa para Builder pero con los aportes del maestro cHackAll hemos saltado a otros entornos.

Por mi no existe inconveniente de considerarlo un tutorial y trasladarlo de foro. No se que opinará cHackAll.

Saludos.

Edito para reparar caracteres dañados en el texto
  • 0

#8 cHackAll

cHackAll

    Advanced Member

  • Administrador
  • 598 mensajes

Escrito 25 mayo 2009 - 12:51

...Por mi no existe inconveniente de considerarlo un tutorial y trasladarlo de foro. No se que opinará cHackAll...


done!

...Posiblemente el compilador de Microsoft sea mejor que el Builder en cuanto opciones de compilación y enlace. Por desgracia no puedo compilar ni jugar con tu ejemplo porque no dispongo del Visual Studio 2008...


Sin duda... es un compilador más poderoso, aquí­ la descarga!

...el Visual C tenga una ventaja respecto a otros compiladores...  ...de no tener que distribuir librerías que vienen de serie en el propio S.O...


No, por ejemplo con Delphi no se necesita redistribuir nada para una aplicación "Hello World". En el otro extremo, cambiando la configuración anteriormente descrita se puede hacer una aplicación dependiente de librerías no dinámicas externas.

...Me gustaría poder probar tu ejemplo y comprobar, como creo, que no se amputan las funciones de las librerías estándar de C/C+ con tu sistema...


Pruébalo amigo, no se modifica ninguna librería crucial pero si se prescinden de las mismas ;)

Salud!

Edito para reparar caracteres dañados en el texto
  • 0

#9 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.831 mensajes
  • LocationMadrid - España

Escrito 26 mayo 2009 - 05:31

O.K. cHackAll, He descargado Visual C++ 2008 y he seguido tu ejemplo. Lo que pretendía averiguar era la duda de si con ese tipo de proyecto podría usar funciones C/C++ estándar como strlen, new o delete.

Al igual que ocurre con el Builder, cuando se hacen este tipo de aplicaciones en miniatura, esas funciones no se pueden usar, al no estar soportadas por la API, precisan de una librería externa. Para Visual C++ 2008 es MSVCR90D.dll.

Me llama la atención como Visual C es capaz de generar en 2K y con mas sencillez, lo que Builder consigue en 4K. De tosas formas ambos compiladores consiguen ejecutables realmente pequeños.

Saludos.

Edito para reparar caracteres dañados en el texto
  • 0

#10 cHackAll

cHackAll

    Advanced Member

  • Administrador
  • 598 mensajes

Escrito 28 mayo 2009 - 09:00

...Me llama la atención como Visual C es capaz de generar en 2K y con mas sencillez, lo que Builder consigue en 4K...


Con tu ejemplo llegar a 2 Kb no es ningún problema; necesitas hacer un "strip relocations" luego de compilarlo o mediante opciones de compilación, y finalmente eliminar sus recursos (inútiles para el caso). Pero como dices ambos compiladores son poderosos... yo personalmente tengo una fuerte inclinación a VS-C utilizando MFC en lugar de BCB y VCL.

Salud!

Edito para reparar caracteres dañados en el texto
  • 0