Decriptar programa ejecutar sus funcione mas rapido en memoria y cpu.

Iniciado por krammbler, 2 Enero 2019, 19:06 PM

0 Miembros y 1 Visitante están viendo este tema.

krammbler

Hola A todos,
Escribo para comentarles a cerca de una idea interesante, que tiene que ver con el poder ejecutar mas rapido en CPU un proceso informatico, filtrando solo las funciones/procesos que me sirven o quiero ejecutar de un programa, y evitando los demas.

Antes que nada una introduccion de como funciona un proceso en un sistema operativo que encontre en la web:
_______________________________________________________

COMO SE CARGA Y EJECUTA UN EXE EN WINDOS?

Se que los programas compilados para Windows nativos tienen un formato llamado Portable executable (PE) y que permite que el sistema operativo cargue el programa a la memoria.



El proceso es relativamente sencillo de explicar, pero no tanto de llevar a cabo.

Lo primero: las CPU tienen una serie de requisitos para poder ejecutar un trozo de código cualquiera: ciertos registros tienen que apuntar a direcciones de memoria válidas.

Adicionalmente, se han de cumplir las normas que imponga el Sistema Operativo de turno: cierto trozo de memoria solo puede ser accedido por cierto programa, y en cierto modo (lectura, escritura, ejecución).

El proceso básico de ejecución (que no de carga) consiste en preparar la memoria y los registros de la CPU para que cumplan los requisitos impuestos. Esto es llevado a cabo por el Sistema Operativo.

Para lo anterior, se consultan ciertos datos del archivo a ejecutar.

Una vez la memoria preparada, se carga el código en si, cada porción en la parte necesaria. Los segmentos de datos del programa se cargan en la zona de memoria dedicada para datos, los segmentos de código ejecutable en la zona de código, ...

Aquí, lo de carga puede ser engañoso. Mediante el proceso de mapeo de archivos en memoria, la antedicha carga se puede posponer hasta que la CPU no necesite acceder realmente a la zona de memoria en cuestión.

Una vez el código cargado, se procede a su enlazado dinámico. Es decir, se consulta el segmento del archivo que contiene el nombre de las librerías .dll que se necesitan. Algunas ya estarán cargadas (en zonas de memoria compartida, con ejecución para todo el mundo). Otras no, y será necesario repetir el proceso de carga para ellas (con sus peculiaridades; por ejemplo, las librerías no necesitan pila (stack).

Por fin, está todo cargado (o *mapeado en memoria). Ahora, hay que sobre-escribir los puntos del código del programa en los que se llama a ciertas funciones importadas, por las direcciones reales de las mismas.

Esto también es engañoso. Hay mecanismos para no tener que recorrer toda la memoria, usando unas zonas de memoria como tablas intermedias. El resultado final, no obstante, es el mismo: todas las llamadas a .dll apuntan al código deseado.

Ahora, ya sí, se puede comenzar la ejecución del programa, forzando a la CPU a crear un nuevo proceso, y usando para ello las zonas de memoria que con tanto mimo se han preparado.

Normalmente, la ejecución en si misma no pasa directamente a la función WinMain, o main, o lo que sea. Se llama primero a un trozo de código colocado por el compilador, que se encarga de realizar tareas de inicialización de los datos. En C++, por ejemplo, es este código pre-main el que se encarga de llamar a los constructores de instancias estáticas.

Ahora si; por fin, llamamos a nuestra WinMain( ).

Una de las cosas que podemos hacer dentro de WinMain( ) es registrar nuestra función de ventana, que será usada por Windows para entregar los mensajes. Estos mensajes se generan independientemente de nosotros, la mayoría de ellos son artificiales (generados por el propio sistema). Pero eso ya es otro tema, y será contada en otra ocasión ...

_______________________________________________________


Ahora bien, mi idea y la que quisera compartir es justamente la de decompilar un programa a nivel HEXA/Binario para poder determinar cual direccion de memoria asigna en cada momento a los procesos de cada una de las funciones especificas que se van a ejecutar, poder filtrar solo las que me interesan en tiempo real y ejecutar solo esas, de forma tal que el proceamiento sera siempre mas rapido y el cpu me devuelva una respuesta mas veloz que ejecutar todo el bloque entero de datos.

Para dejarlo mas claro agrego una imagen:


Ahora bien quisiera saber si alguien tiene algun tipo de experiencia en esto y que les parece??? si alguno de ustedes intento algo parecido o si estan en condicione de ayudarme con un proyecto que me gustaria iniciar con un amigo.

Quizas me falto informacion o puse informacion erronea, si es asi pido disculpas por la ignorancia! je  :rolleyes:






[.b][.i][.u]Liberemos el conocimiento antes de que sea tarde.[/u][/i][/b]

Serapis

Evidentemente aunque hayas leído algo de información al respecto, estás lejos de haber entendido el proceso.

- De entrada, no todos los ejecutables están en formato PE, se puede compilar a código nativo.

- Cuando compilas un programa, si el compilador detecta que se importa una librería pero que luego nunca se llama (queda desierta), en general (suele ser opcional), descartará esas referencias y por tanto en el ensamblado final no hay noción a ellas. Es decir no tiene que resolver direcciones de funciones que ni siquiera serán invocadas, porque aún declaradas, no aparecen llamadas para usarlas en el código.

- Cuando se compila un programa, las direcciones de variables, funciones etc... son relativas, es decir el programa comienza la dirección 0 para la primera instrucción del programa (a grosso modo), luego cuando el loader (cargador), actúa localiza  memoria libre en el equipo, supongamos que es: 0010346AB1, pués el loader tiene como misión hacer las direcciones locales absolutas añadiendo ese valor a todas las direcciones relativas en el programa. Y esto se hace sí o sí siempre excepto que el programa sea un '.com', lo hace el S.O. Además es algo muy rápido... a no ser que tengas un ejecutable de 500Mb.

- Si bien el cargador se encarga de las direcciones relativas, fue el linker, el que se encargó de las direcciones de las librerías externas, y es ahora el loader, quien de nuevo tiene que resolver esas direcciones relativas a absolutas... y en el caso de que una librería no esté cargada ya en memoria debe cargarla, para así poder tomar su dirección. Aún así, los objetos se cargan a medida que se usan, es decir en tiempo de ejcución todavía queda por resolver algunas direcciones, pués para ciertos objetos ni siquiera se conoce su naturaleza. Estos enlaces selen llamarse 'Late binding', y para ellos si hay impacto en tiempo (en relación comparativa a 'early binding'), pero sigue siendo inapreciable a efectos humanos...

- La cpu, no tiene que "resolver" todas las funciones, a no ser que con 'resolver' quieras decir formalizar sus direcciones, que entonces sí es así, pero si con 'resolver' te refieres a ejecutar, no.
Solo se invoca y ejecuta el código que está en ese preciso instante en la CPU (el programa yace en memoria), en realidad una única instrucción a la vez (salvo paralelismo)... Es decir en un instante dado, la CPU sólo atiende a un único proceso (dejamos aparte los cores que tenga), da igual que tu programa tenga 20 funciones o solo 2, que llame a chorrocientas funciones externas o a ninguna, solo 1 proceso se está ejecutando en la CPU a la vez, pero no todo es tan así... hay algo llamado pipeline (cascada), que no es más ni menos que una especie de túnel de ejecución de modo que cuando una instrucción se está ejecutando, otra se está preparando, etc... similar a un ssistema de montaje en serie de coches, por ejemplo... Así, la cascada tiene varios niveles, además hay sistemas de predicción (para eso sirve la caché de la CPU), cuando se equivoca o se cambia de proceso, se tiene que limpiar toda la cascada... (algo así como si en el sistema d emontaje de coches deciden cambiar el modelo del vehículo, o incluso algo tan nimio como la pintura) y guardar en la pila el estado actual de la CPU para ese proceso, hasta su próximo turno.... esto siempre supone un pequeño retraso.

- Si un programa tiene (pongamos) 300 referencias de direcciones de librerías que resolver,  el impacto de carga es nulo (no observable a nivel humano), un pestañeo y ya...

Donde si se observa un notable impacto, es en dos casos muy concretos y claros:
--- Cuando se procede con código interpretado. El código interpretado puede ser entre 10 y 100 veces más lento, depende del intérprete.
--- Cuando previo a la ejecución hay una compilación (una traducción del bytecode (código intermedio igual que PE, pero por ejemplo en NET es CIL)). En NET, el caso es más severo (que en COM), ya que el código al compilar (desde el IDE) lo hace en código CIL, y es en tiempo de ejecución cuando CLR lo compila a código nativo, optimizado para el procesador que calza el equipo... Es un pequeño sobrecoste de carga a cambio de una mayor optimización personalizada para la CPU presente en el equipo en cuestión (durante toda la ejecución del programa), luego... quién tiene motivos de queja por ello... con todo, puede forzarse para programas que asíduamente se usan en un equipo dado, se compilen a código nativo... de hecho hay un servicio en el equipo que se encarga de ello: Microsoft .NET Framework NGEN. Este caso también se da con los programas en JAVA

Los compiladores modernos siguen el esquema UNCOL de Tanenbaum, que aunque jamás llegó a término, si mostró el camino a seguir. Actualmente una especificación similar es el CLI (Common Language Infrastructure) de Mocosoft.
Desde diferentes lenguajes, se compila a un código intermedio (CIL para NET), y luego desde código intermedio al código nativo en la máquina destino, así basta un único ejecutable para diferentes máquinas. Y a su vez puede haber un ecosistema de lenguajes aprovechandose de las mismas características del compilador, por ejemplo en NET tienes Visual Basic, C#, F#, Q#,  el suspendido J#, incluso uno mismo podría escribir su propio lenguaje y compilador.


Por último, señalarte que no tiene nada que ver el tiempo de carga de un programa con el rendimiento durante la ejecución del programa o de una determinada funcionalidad del mismo o llamada externa.

Yuki

Los compiladores ya descartan código no utilizado. La única manera de optimizar un ejecutable es utilizar los opcodes apropiados (en vez de mov eax,0 [B8,00,00,00,00] usar xor eax,eax [33,C0]).

Y eso se aplicaría únicamente en el código que el compilador no descarte.

Estas optimizaciones son mas generales en lenguajes de bajo-medio nivel, podrías realizar tu cometido, pero en lenguajes interpretados mas que nada.