Detectar código compilador de un exe

Lenguaje de bajo nivel

Pueden ser:

  • Lenguaje máquina.
  • Lenguaje ensamblador.

El lenguaje máquina es código binario y actúa directamente sobre el hardware. Es el único lenguaje que no necesita traducción ya que el procesador reconoce las instrucciones directamente.

La codificación en lenguaje ensamblador es mnemotécnica, es decir, utiliza etiquetas para describir ciertas operaciones. No es más que una traducción de cadenas de 0 y 1 casi imposibles de recordar por nombres que permiten identificar las instrucciones. Es necesario traducir el código ensamblador al lenguaje máquina para que el procesador reconozca las instrucciones.

Lenguajes de alto nivel

Tienden a acercarse al lenguaje humano. Es por eso que necesitan traducirse a lenguaje máquina. Esta traducción hace que la ejecución sea más lenta que en los lenguajes de bajo nivel pero, al no depender del procesador, se pueden ejecutar en diferentes ordenadores.

Clasificación por generaciones

Pueden clasificarse en 5 generaciones:

  • 1ª generación: lenguaje máquina y ensamblador.
  • 2ª generación: formada por el lenguaje macroensamblador, que es el lenguaje ensamblador combinado con instrucciones de control y manejo de datos más complejos. Cada modelo de ordenador tiene un lenguaje ensamblador propio, distinto de los demás, por lo que el programa solo puede utilizarse en la máquina para la cual se programó.
  • 3ª generación: formada por la mayor parte de los lenguajes de alto nivel actuales. El código es independiente de la máquina y el lenguaje de programación es parecido al lenguaje humano.
  • 4ª generación: formada por lenguajes y entornos diseñados para una tarea o propósito muy específico como acceso a bases de datos, generación de interfaces de usuario, etc. Ejemplos: SQL, Informix.
  • 5ª generación: está formada por los lenguajes en los que el programador establece el problema a resolver y las condiciones a cumplir. Se usa en inteligencia artificial, sistemas expertos, etc. Ejemplos: Prolog, Smaltalk y Lisp.

Clasificación por el paradigma de la programación

Un paradigma de programación es una filosofía de programación con un “núcleo central” que los lenguajes que utilizan el mismo paradigma de programación habrán de seguir. Pueden clasificarse en 2 grandes grupos: paradigma imperativo y paradigma declarativo.

Paradigma imperativo

El código está formado por una serie de instrucciones para realizar una tarea organizando o cambiando valores en memoria. El programador indica cómo obtener el resultado deseado. Las instrucciones se ejecutan de forma secuencial. Ejemplos: Java, C, C++, PHP, Javascript y Visual Basic.

Se distingue entre los que siguen la metodología estructurada y los que siguen la metodología orientada a objetos. El teorema de “programa estructurado” demuestra que todo programa puede escribirse utilizando solo las siguientes estructuras:

  • Secuencial
  • Alternativa (basada en una decisión)
  • Repetitiva (bucles)

La metodología estructurada evoluciónó añadiendo el módulo como componente básico dando lugar a la programación estructurada y modular. Los programas estaban formados por módulos y datos. Los módulos necesitan unos datos de entrada y obtienen otros de salida que a su vez podrán ser utilizados por otros módulos. Ejemplo: C.

No debemos medir la complejidad de una aplicación basándonos en el número de líneas de código. Hay otros muchos factores que influyen decisivamente como, por ejemplo, la complejidad del algoritmo que se codifica. Teniendo esto en cuenta, haremos una estimación de la complejidad del software basándonos solo en el número de líneas para ilustrar la importancia de dividir el código en módulos.

Uno de los métodos fundamentales para resolver un problema es dividirlo en problemas más pequeños. Estos problemas pueden a su vez ser divididos repetidamente en problemas más pequeños. El método de diseño se denomina diseño Top-Down.

¿En qué nos debemos basar para realizar la división de un código en distintos módulos?

Nos basamos en métricas que miden la calidad estructural.

  • Cohesión: Indica la relación que existe entre los elementos del mismo módulo. Los elementos de un módulo deben tener la máxima relación entre sí.
  • Acoplamiento: Es el grado de interdependencia de los módulos. El objetivo es lograr módulos tan independientes como sea posible.

En los 80 aparecíó la metodología orientada a objetos. Es la que considera el objeto como elemento básico. Se popularizó a principios de los 90. Ejemplos: Java, C++. Cada objeto sigue el patrón especificado en una clase.

Una clase es un “molde” a partir del que se crean los objetos.

La clase está compuesta de atributos (datos) y métodos (procesos). Pueden tener las siguientes propiedades:

  • Herencia: pueden declararse clases hijas que heredan las propiedades y métodos de la clase padre.
  • Los métodos heredados pueden sobrecargarse, es decir, dentro de la misma clase puede aparecer definido con distinto comportamiento el mismo método con diferente número y/o tipo de parámetros.
  • Polimorfismo: carácterística que permite que un método se comporte de diferente manera dependiendo del objeto sobre el que se aplica.
  • Encapsulamiento: Permite ocultar detalles internos de una clase.

Paradigma declarativo

El código indica qué es lo que se quiere obtener pero no cómo se tiene que obtener. El programador tiene que pensar en los resultados que precisa pero no en la secuencia de órdenes para que se lleve a cabo. Ejemplos: Lisp, Prolog o SQL.

Clasificación por la forma de traducirse a lenguaje máquina y ejecutarse

Se llama código fuente al código escrito en un lenguaje de programación mediante una herramienta de edición.
Este código se guarda en un archivo y tendrá que traducirse a lenguaje máquina para poder ser ejecutado. La traducción puede hacerse mediante compiladores y/o intérpretes.

Pueden considerarse 3 grupos de lenguajes de programación teniendo en cuenta la forma de traducirse a lenguaje máquina y ejecutarse:

  • Lenguajes compilados.
  • Interpretados.
  • Máquina virtual

Lenguajes compilados

Disponen de un compilador que traduce el código fuente a código máquina, y guardan el resultado en un archivo ejecutable. Con ese archivo se puede ejecutar el programa cuantas veces sea necesario sin tener que repetir el proceso de generación. El tiempo de espera entre ejecución y ejecución es ínfimo.

Lenguajes compilados – Carácterísticas

El proceso de traducción se hace en tiempo de compilación y una sola vez. La ejecución es muy rápida ya que el ejecutable está en lenguaje máquina. El ejecutable sólo funciona para la plataforma para la que fue creado. El usuario que lo ejecuta no tiene que conocer el código fuente. El programador tiene el código fuente más protegido.

El ejecutable no se generará si existen errores léxicos, sintácticos o semánticos. Interrumpir la ejecución puede  afectar a la plataforma.

La modificación del código fuente implica volver a generar el archivo ejecutable. Se necesita un programa denominado enlazador que identifique las referencias a nombres externos en el programa objeto.

Lenguajes compilados – Ejemplos

  • C
  • Pascal
  • Cobol

Lenguajes interpretados

Ejecutan las instrucciones sin que se genere código objeto. Necesitan un intérprete en memoria para que en tiempo de ejecución se vaya traduciendo el código fuente a lenguaje máquina.

El código fuente es traducido por el interprete a un lenguaje entendible para la máquina paso a paso, instrucción por instrucción. El proceso se repite cada vez que se ejecuta el programa.

Lenguajes interpretados – Carácterísticas

  • No existe un archivo ejecutable.
  • El proceso de traducción se hace cada vez que se ejecuta
  • La ejecución es lenta.
  • El archivo puede ejecutarse en diferentes plataformas siempre que exista intérprete para ellas.
  • Interrumpir la ejecución solo afecta al intérprete, y no a la plataforma.

Lenguajes interpretados – Ejemplos

  • Ruby.
  • Python.
  • PHP.
  • JavaScript.

No es necesario que el usuario final posea el compilador para ejecutar el programa. Solo necesitan un navegador actualizado y conexión a Internet.

Lenguajes de máquina virtual

Los lenguajes de máquina virtual hacen una compilación del código fuente pero, en lugar de compilar a código máquina, compilan a código intermedio. El código intermedio tiene varias carácterísticas:

  • Solo se genera si no se encuentran errores en el código fuente
  • Es independiente de la plataforma.
  • La máquina virtual, que es independiente de cada sistema, es la encargada de convertir el código intermedio al lenguaje máquina de la plataforma donde se quiera ejecutar.

En el caso de lenguajes como Java:

En tiempo de compilación el compilador Java genera el código intermedio llamado bytecode, independiente de la plataforma. En tiempo de ejecución, necesita una máquina virtual Java que interprete el bytecode. Esta máquina virtual simula una máquina real.

Esta metodología aporta muchas de las ventajas de la compilación y la interpretación. El código intermedio ya está libre de errores sintácticos, y es muy sencillo. Si existe un intérprete para este código en distintas plataformas, el mismo código se puede ejecutar en cada una de ellas.Su ejecución más rápida, ya que no ha de comprobar la sintaxis. El código intermedio no es ejecutado por una CPU real, sino por una CPU virtual: el intérprete.

En lenguajes de ejecución administrada como los de la plataforma .NET:

En tiempo de compilación, el compilador genera el código independiente de la plataforma. Para ejecutar el código es necesario tener en el equipo cliente la versión de .NET Framework adecuada y se hace la última fase de compilación, en la que el CRL traducirá el código intermedio a código máquina mediante un compilador JIT (Just In Time).

Existe un código intermedio que se ejecuta en un software específico pero no es un ejecutable. El proceso de traducción inicial se hace antes de la ejecución y el de la traducción final se hace cada vez que se ejecuta. La ejecución no es tan rápida como en los lenguajes compilados pero es más rápida que en los lenguajes interpretados.

Los errores de tipo léxico, sintáctico o semántico se detectan en fase de compilación, y si existen no se generará el código intermedio. Interrumpir la ejecución sólo afecta al intérprete y no a la plataforma. La modificación del código fuente implica volver a repetir el proceso de compilación.

Clasificación en la arquitectura cliente-servidor

La arquitectura cliente-servidor consiste en un programa cliente que realiza peticiones a un servidor. Es más útil cuando el cliente o el servidor están comunicados mediante una red (aunque también se puede aplicar cuando están en la misma máquina).

Se diferencia entre lenguajes que se ejecutan:

  • Del lado del servidor, como PHP
  • Del lado del cliente, como Javascript

Lenguajes más difundidos

Los lenguajes comunes a todos estos índices en el año 2019 en los primeros puestos son : C, C++, Java, C#, Python y Javascript.

C

Es un lenguaje creado por Dennis Ritchie para codificar Unix. Catalogado como un lenguaje de alto nivel, estructurado y modular, pero con muchas carácterísticas de bajo nivel como utilizar punteros para hacer referencia a una posición física de memoria.

Influyó en el diseño de C++, Java, PHP, Javascript, etc.

C++

Diseñado en 1980 por Bjarne Stroustrup para extender C con tecnología OOP.

Java

En un lenguaje de OOP desarrollada por Sun Microsystems en 1995. Hereda mucha sintaxis de C y C++ pero con un modelo de objetos más simple. También elimina las herramientas de bajo nivel.

C#

En un lenguaje de OOP desarrollada Microsoft en el año 2000 como parte de la plataforma .NET.

PHP

Es un lenguaje interpretado del lado del servidor que se puede usar para crear cualquier tipo de programa, aunque donde tiene más popularidad es en la creación de páginas web dinámicas. La ejecución de código PHP en un servidor suele generar código HTML que se envía a los navegadores de los clientes.

Python

Es un lenguaje de programación interpretado, con una sintaxis limpia que favorece que el código sea legible. Es un lenguaje de código abierto.

Javascript

Es un lenguaje orientado a objetos, basado en prototipos, débilmente tipado, y dinámico. Se utiliza en el lado del cliente y está implementado como parte de un navegador web, aunque también existe una versión del lado del servidor.

Proceso de generación del código

La generación de código consta de tres partes: edición, compilación y enlace.

Edición

Esta fase consiste en escribir el algoritmo de resolución mediante un editor de texto o herramienta incluida en un entorno de desarrollo. El código resultante es el código fuente.

Compilación

Consiste en analizar el código fuente mediante un compilador, para obtener, si no se encuentran errores, el código objeto o código intermedio multiplataforma. Los análisis realizados son:

  • Análisis léxico: lee el archivo fuente carácter por carácter y forma grupos de caracteres con un significado léxico mínimo (tokens). Elimina los componentes no esenciales del programa fuente, e ignora todo lo que no sea necesario en fases posteriores.
  • Análisis sintáctico: utiliza los tokens encontrados por el analizador léxico y comprueba si llegan en el orden correcto proporcionado por la gramática del lenguaje.
  • Análisis semántico: se ocupa de comprobar el significado de las sentencias. Puede haber sentencias sintácticamente correctas pero que no puedan ejecutar por no tener ningún sentido.,>

Síntesis. Permite:

La generación de código intermedio independiente de la máquina. Algunos lenguajes compilados, como C, pasan antes por una fase de preprocesamiento en las que se lleva a cabo operaciones como sustituir las constantes por el valor o incluir archivos de cabecera. Otros, como Java, generan Bytecode generan Bytecode Java que podrá ser ejecutado en una JVM.

La traducción del código intermedio anterior al código máquina para obtener el código objeto. Esta traducción lleva consigo también una optimización de código.

Enlace

Consiste en enlazar el archivo objeto obtenido en la compilación con módulos objetos externos para obtener, sino se encuentran errores, el ejecutable.

El archivo obtenido en la compilación puede tener referencias a códigos objeto externos que forman parte de bibliotecas externas estáticas o dinámicas. Si la biblioteca es estática, el enlazador añade códigos objeto de las bibliotecas al archivo objeto, por lo que el archivo ejecutable resultante aumenta de tamaño con relación al archivo objeto, pero no necesita nada más que el SO para ejecutarse.

Si la biblioteca es dinámica, el enlazador añade referencias a la biblioteca, por lo que el archivo ejecutable resultante apenas aumenta de tamaño con relación al archivo objeto, pero la biblioteca dinámica tiene que estar accesible cuando el archivo ejecutable se ejecute.

Ejecución

Si el lenguaje es interpretado será necesario el archivo fuente y el intérprete para que este vaya traduciendo y ejecutando cada instrucción del archivo fuente a lenguaje máquina. Ej. Python

Si el lenguaje es compilado será necesario el archivo ejecutable, y en algunos casos también se necesitan bibliotecas dinámicas. Ej. C

Si el lenguaje necesita de máquina virtual, será necesario tener el código intermedio y la máquina virtual, para que vaya traduciendo y ejecutando el código intermedio a lenguaje máquina. Ej. Java