El código máquina es un programa informático escrito en lenguaje máquina. Utiliza el conjunto de instrucciones de una determinada arquitectura informática. Suele estar escrito en binario. El código máquina es el nivel más bajo del software. Otros lenguajes de programación se traducen a código máquina para que el ordenador pueda ejecutarlos.

El código máquina está formado por secuencias de bits organizadas en bytes y palabras de longitud determinada por la arquitectura (por ejemplo 8, 16, 32 o 64 bits). Aunque su representación interna es binaria, habitualmente se visualiza y edita en hexadecimal para facilitar la lectura por humanos. Los ficheros ejecutables contienen secciones con código máquina, tablas de símbolos y metadatos que permiten al sistema operativo cargar y ejecutar el programa.

Formato de una instrucción

Una instrucción indica al procesador qué operación debe realizar. Cada instrucción se compone de un opcode (código de operación) y uno o más operandos. Los operandos suelen ser direcciones de memoria, registros del procesador o datos inmediatos.

Existen diferentes esquemas de codificación según la arquitectura:

  • Longitud fija: cada instrucción ocupa el mismo número de bytes (común en arquitecturas RISC).
  • Longitud variable: las instrucciones pueden tener tamaños distintos (típico en arquitecturas CISC como x86).
  • Modos de direccionamiento: inmediato, directo, indirecto, indexado, registro, relativo, etc., que determinan cómo se interpreta el operando.

Las instrucciones se representan en código máquina mediante valores numéricos para el opcode y campos para los operandos; los lenguajes ensamblador ofrecen mnemónicos legibles por humanos que luego se traducen (ensamblan) a esos códigos binarios.

Cómo se genera y ejecuta

Los creadores de programas convierten el código en otro lenguaje o código máquina. El código máquina se denomina a veces código nativo. Se utiliza cuando se habla de cosas que sólo funcionan en algunos ordenadores.

Las rutas habituales para generar código máquina son:

  • Compilación: el compilador traduce código fuente a código máquina (a menudo pasando por ensamblador y enlazador). El texto original incluía el enlace a que otros lenguajes compilan o interpretan en código máquina.
  • Ensamblador: traduce código ensamblador (mnemónicos) a instrucciones binarios.
  • Intérpretes y máquinas virtuales: algunos entornos ejecutan instrucciones de un lenguaje intermedio (bytecode) y generan código máquina en tiempo de ejecución mediante JIT.

En tiempo de ejecución, el procesador realiza el ciclo de búsqueda-decodificación-ejecución (fetch-decode-execute) sobre las instrucciones almacenadas en memoria. El cargador del sistema operativo coloca el código en memoria y gestiona permisos (por ejemplo, marcar páginas como ejecutables).

Portabilidad y dependencias de arquitectura

El código máquina depende directamente del diseño del procesador: del conjunto de instrucciones y de los detalles microarquitectónicos. Por eso un binario compilado para una arquitectura no funciona en otra sin emulación o traducción. Esta dependencia es la razón por la que se habla de código nativo y de la necesidad de recompilar o redistribuir versiones para diferentes plataformas (x86, ARM, MIPS, etc.).

Representación, edición y análisis

Además del binario puro, el código máquina se maneja con herramientas específicas:

  • Desensambladores: traducen código máquina a mnemónicos legibles para análisis y depuración.
  • Depuradores: permiten ejecutar paso a paso, inspeccionar registros y memoria.
  • Hex editors y utilidades como hexdump para visualizar y editar bytes en disco.
  • Enlazadores y relocadores: resuelven referencias entre módulos y ajustan direcciones en el código final.

Seguridad y comportamiento avanzado

El manejo y la ejecución de código máquina están estrechamente relacionados con la seguridad del sistema. Algunas consideraciones importantes:

  • Ejemplo de riesgos: desbordamientos de búfer, inyección de código y ejecución de código no autorizado.
  • Protecciones: DEP/NX (prohibir ejecución en ciertas páginas), ASLR (aleatorización de direcciones), firmas y control de integridad.
  • Código auto-modificante y microcódigo: algunos programas (o firmware) modifican instrucciones en tiempo de ejecución; además, algunos procesadores usan microcódigo para implementar instrucciones complejas.
  • Ingeniería inversa: análisis de código máquina con fines de depuración, auditoría o descubrimiento de vulnerabilidades.

Ejemplos prácticos y diferencias entre arquitecturas

Por ejemplo, las arquitecturas x86 suelen tener instrucciones de longitud variable y muchas modalidades complejas, mientras que arquitecturas RISC como ARM o RISC-V usan formatos más regulares y a menudo instrucciones de longitud fija, lo que simplifica el pipeline del procesador. Algunas variantes (como ARM Thumb) combinan instrucciones de 16 y 32 bits para equilibrio entre densidad y rendimiento.

En resumen, el código máquina es la representación binaria de las instrucciones que entiende un procesador concreto. Es el vínculo final entre el software escrito por humanos y las operaciones eléctricas y lógicas que realiza el hardware.