> ## Content Index
> Fetch the complete content index at: https://rootedcon.ghost.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# Conocer el proceso de arranque: bootloader en BIOS Legacy/x86 [1 de 5]
- URL: https://rootedcon.ghost.io/conocer-el-proceso-de-arranque-bootloader-en-bios-legacy-x86-1-de-5/
- Published: 2026-10-14T13:09:41.000Z
- Updated: 2026-10-14T13:09:41.000Z
- Author: RootedCON

Por [Román Ramírez](https://www.linkedin.com/in/rramirez/)

¿Por qué puede tener sentido dedicar tiempo a esto? Sobre todo por comprender cómo funciona el proceso de arranque de los sistemas operativos.

¿Tiene utilidad práctica leer sobre bootloaders en BIOS legacy? Depende. Si tu interés es aprender sobre el arranque en sistemas modernos, no, no tiene ninguna utilidad.

Pero, si quieres saber cómo funciona el proceso y, sobre todo, estar preparado para todo cuando te encuentres con sectores de arranque diversos… en ese caso sí, tiene mucho interés.

### El arranque en BIOS Legacy

En los sistemas clásicos, el proceso de arranque seguía este esquema aproximadamente:

![](https://storage.ghost.io/c/4c/aa/4caac146-d681-438d-a4da-6d3f4f3a24a1/content/images/2026/10/data-src-image-1582894d-12f7-4de7-b947-4fd566c586ca.png)

*Proceso desde el POST hasta el Sistema Operativo*

Desde el momento que pulsas el botón de arranque hasta que llegas al sistema operativo y su proceso de carga e inicio, pasan un montón de cosas.

Podrías pensar que lo primero que se ejecuta es el POST (Power-On Self Test), que es un proceso de autocomprobación en el encendido que se centra en comprobar que todo funciona correctamente. Verifica la memoria RAM y otros elementos hardware.

Pero, hazte la siguiente pregunta: si veo por pantalla mensajes de este proceso POST… ¿no quiere esto decir que algo se ha iniciado ANTES de entrar en esa etapa? Ese algo, naturalmente, es un mínimo de gestión de la tarjeta de video. Es decir, hay un mini-inicio previo al propio POST y la carga de la BIOS que permite establecer lo mínimo para poder emitir mensajes por pantalla.

Y, ojo, esto no es necesariamente lo único que puede arrancar e iniciarse. Es muy importante tener en cuenta que esto OCURRE ANTES del proceso más conocido de la BIOS:

1. **Arranque de microcódigo de la CPU y el Reset Vector**: cuando llega la señal de power-good, la CPU comienza en un estado conocido y salta a la posición de memoria del reset vector que es en x86 legacy 0xfffffff0 (16 bytes antes del final del espacio direccionable de 4GB).
2. **Luego, la CPU entra en Modo Real**, lo que cambia a direccionamiento usando segmento:offset. Con **CS=0xf000** y el **EIP=0xfff0**, apuntando a la dirección (física) efectiva **0xffff0**.

Los chipset mapean esta región directamente al chip de flash SPI donde reside el firmware. Lo que quiere decir que están ocurriendo “cosillas” en etapa temprana.

PERO, ojo, es que además en equipos Intel arranca **Intel Management Engine (ME)**: es un procesador ARC (o Sparc en versiones recientes) que está embebido en el PCH (Platform Controller Hub), que tiene su propio firmware en una partición del mismo chip SPI flash y que corre un sistema operativo real-time (RTOS) que, actualmente es una versión de Minix. Puede estar en posiciones de memoria de la flash SPI en offsets 0x1000 a 0x500000 dependiendo de la versión concreta.

En AMD lo mismo con el **AMD Platform Security Processor (PSP)**, con un procesador ARM Cortex-A5 con TrustZone que arranca ANTES del contexto x86, con el firmware en el flash SPI, (en una región separada de la BIOS) y que puede estar en el offset 0x20000.

Resumiendo: que antes incluso de que puedas ejecutar una sola instrucción x86, ya están pasando “cosas” en tu equipo.

Hay además una Inicialización de memoria (pre-vídeo) con Cache-as-RAM (CAR) —la CPU usa su caché L2/L3 como RAM temporal— y código almacenado en la flash SPI que se ejecuta directamente (XIP, eXecute In Place).

Y luego, ya si eso, en legacy inicias la VBIOS (0xc0000 - 0xc7fff) y en UEFI inicias el GOP (Graphics Output Protocol).

Así que, repito, antes de que llegues a ejecutar una sola instrucción de ASM x86 han pasado muchísimas cosas de las que puede que no tengas demasiado control. Hazte preguntas :)

## El Master Boot Record (MBR)

En el arranque de BIOS legacy, una vez la BIOS (y todo lo que ha pasado antes de ella) está preparada, lee el primer sector de los discos que tiene disponibles, buscando una firma muy concreta que cierre los 512 bytes (un sector, normalmente, es de 512 bytes): **0xaa55**.

Si un disco tiene esos dos bytes al final del primer sector, es decir, en las posiciones 510 y 511 (empezando a contar en 0, claro), la BIOS decide que es un disco de arranque. De ahí que tengamos, naturalmente, un orden de arranque definido: puede haber varios discos con MBR válidos para arrancar.

¿Cómo funciona el sector de arranque? No tiene demasiada complejidad real: es un programa que tiene que caber en 512 bytes teniendo en cuenta que dos bytes van a estar ocupados por la firma al final (0xaa y 0x55).

Debe ser obvio que los arranques, los bootloaders, no tienen toda la lógica dentro de esos 512 bytes, sino que funcionan en multi-etapa (multistage). Una primera etapa lo que hace es cargar los datos de la segunda etapa para, una vez cargado el código, transferir la ejecución a un nuevo punto (la posición de memoria de inicio de la segunda etapa).

Antes de entrar en detalles sobre cómo cargar en multietapa, veamos un bootloader sencillo:

[![](https://storage.ghost.io/c/4c/aa/4caac146-d681-438d-a4da-6d3f4f3a24a1/content/images/2026/10/image-1.png)](https://drive.google.com/file/d/1iNpf18HR066u9%5FX3rbGNduGiwR-vDA%5F7/view)

Link: [simple0.asm](https://drive.google.com/file/d/1iNpf18HR066u9%5FX3rbGNduGiwR-vDA%5F7/view)

¿Qué tenemos en este código? Vayamos por partes con explicaciones para “para tontos”:

1. **Lo primero de todo el código que vemos debe ser ensamblado con nasm**: es un ensamblador que convierte las instrucciones en ASM en código ejecutable. Hay otros ensambladores como pueden ser FASM, FASMG, YASM, MASM… nosotros vamos a utilizar nasm para los ejemplos de estos artículos.
2. **\[BITS 16\]**: es una DIRECTIVA DE ENSAMBLADO (no una instrucción) con la que indicamos que el código debe ser ensamblado con operaciones de 16 bits, como corresponde al arranque en *Modo Real* (ahora lo explicaremos).
3. **\[ORG 0x7c00\]**: es otra directiva de ensamblado que le dice a nasm que las direcciones de memoria globales deben referir a 0x7c00 y no a 0x0000\. Por ejemplo, el MAGIC NUMBER que se ubica en los offset 510 y 511, en realidad estarían en 0x7c00 + 510 y 0x7c00 + 511, es decir, 0x7c00 + 0x01fe (0x7dfe) y 0x7c00 + 0x01ff (0x7dff).
4. **start:**: es una etiqueta que, simplemente, indica la posición de memoria que ocupa. Como hemos indicado un origen en 0x7c00 y no hay nada antes de ese start, la etiqueta la asociamos con esa dirección exactamente.
5. **jmp $**: esta sí es una instrucción en ensamblador. Es un salto incondicional (es decir, salta sí o sí) con destino a **$** que, en nasm (y en otros ensambladores) se refiere a la misma dirección donde se ejecuta la instrucción. Dado que lo único que tenemos antes es la etiqueta start, ese $ equivale a la dirección origen 0x7c00\. Es decir, realmente en la posición de memoria 0x7c00 tenemos una instrucción jmp que salta a 0x7c00\. Un bucle infinito saltando a sí misma.
6. **times 510-($-$$) db 0**: esta directiva es muy interesante. La usamos para rellenar con 0 (nulos) todo el tamaño de los 512 bytes menos lo ya ocupado por las instrucciones previas (el jmp que ocupa 2 bytes). La sintaxis es “times <N> <instrucción>” que implica repetir <instrucción>, N veces. En este caso, <instrucción> es “db 0” (define un byte con valor 0) y N es el cálculo de 510 (512 bytes, menos los 2 bytes de la firma 0xaa55 del final) restando el tamaño del código desde la posición actual ($) hasta el origen de la sección en la que estamos ($$). Esto, sabiendo que “jmp $” ocupa 2 bytes, rellenará con 508 bytes de 0…
7. **dw 0xaa55**: y terminará con 0xaa55.

Haciendo un diagrama muy sencillo de la memoria, podemos ver que:

![](https://storage.ghost.io/c/4c/aa/4caac146-d681-438d-a4da-6d3f4f3a24a1/content/images/2026/10/data-src-image-389bd6e3-5bd8-4f4a-bbc8-b31125b60bf6.png)

Y ya está :) Esto es un sector de arranque perfectamente válido y perfectamente funcional. Generar el código ejecutable a partir de este fuente en ensamblador es tan sencillo como:

| $ nasm -f bin simple0.asm -o simple.bin$ ls -la simple.bin\-rw-rw-r-- 1 user user 512 ene 1 00:01 simple.bin$ |
| ------------------------------------------------------------------------------------------------------------- |

Vemos que se ha generado un fichero de salida de, exactamente, 512 bytes. Hemos pasado la opción “-o simple.bin” a nasm para que genere el resultado en ese fichero.

Si usamos hexdump, podemos ver el contenido de este archivo:

| $ hexdump -C simple.bin00000000 eb fe 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \|................|00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................|\*000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa | ..............U.|00000200 |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------- |

En el offset 00000000 encontramos los bytes 0xeb y 0xfe que corresponden con el resultado de ensamblar “jmp $”. Luego tenemos 508 bytes a 0 y, finalmente, en 0x1fe el valor 0x55 y en 0x1ff el valor 0xaa (recuerda que en Intel estamos en little-endian, por lo que para obtener el valor 0xaa55 lo guardas invertido como puedes ver **55 aa**).

Podríamos ejecutar ese fichero .bin directamente con QEMU, por ejemplo:

| $ qemu-system-i386 simple.bin WARNING: Image format was not specified for 'simple.bin' and probing guessed raw. Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted. Specify the 'raw' format explicitly to remove the restrictions. |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Lo que nos abrirá la ventana de la máquina virtual no haciendo nada realmente:

![](https://storage.ghost.io/c/4c/aa/4caac146-d681-438d-a4da-6d3f4f3a24a1/content/images/2026/10/data-src-image-116d6ec7-180d-4079-a2de-743108aa9cd9.png)

Si prefieres usar Bochs en vez de QEMU, podríamos crear un fichero src para iniciar Bochs:

| $ vim bochsrcmegs: 32romimage: file=$BXSHARE/BIOS-bochs-latestvgaromimage: file=$BXSHARE/VGABIOS-lgpl-latestfloppya: 1\_44=simple.bin, status=insertedboot: floppylog: bochs.log$ bochs -f bochsrc 00000000000i\[ \] LTDL\_LIBRARY\_PATH not set. using compile time default '/usr/lib/x86\_64-linux-gnu/bochs/plugins'\======================================================================== Bochs x86 Emulator 2.7 Built from SVN snapshot on August 1, 2021 Timestamp: Sun Aug 1 10:07:00 CEST 2021\========================================================================00000000000i\[ \] BXSHARE not set. using compile time default '/usr/share/bochs'00000000000i\[ \] lt\_dlhandle is 0x64d3cccb77f000000000000i\[PLUGIN\] loaded plugin libbx\_speaker.so00000000000i\[ \] lt\_dlhandle is 0x64d3cccb7a9000000000000i\[PLUGIN\] loaded plugin libbx\_serial.so00000000000i\[ \] lt\_dlhandle is 0x64d3ccc92b9000000000000i\[PLUGIN\] loaded plugin libbx\_unmapped.so00000000000i\[ \] lt\_dlhandle is 0x64d3ccc9347000000000000i\[PLUGIN\] loaded plugin libbx\_gameport.so00000000000i\[ \] lt\_dlhandle is 0x64d3ccc93cf000000000000i\[PLUGIN\] loaded plugin libbx\_extfpuirq.so00000000000i\[ \] lt\_dlhandle is 0x64d3ccc9453000000000000i\[PLUGIN\] loaded plugin libbx\_biosdev.so00000000000i\[ \] lt\_dlhandle is 0x64d3ccc94e9000000000000i\[PLUGIN\] loaded plugin libbx\_iodebug.so00000000000i\[ \] lt\_dlhandle is 0x64d3ccc956f000000000000i\[PLUGIN\] loaded plugin libbx\_parallel.so00000000000i\[ \] reading configuration from bochsrc.txt00000000000i\[ \] lt\_dlhandle is 0x64d3ccc977f000000000000i\[PLUGIN\] loaded plugin libbx\_textconfig.so00000000000i\[ \] lt\_dlhandle is 0x64d3ccc97fc000000000000i\[PLUGIN\] loaded plugin libbx\_x\_gui.so00000000000i\[ \] installing x module as the Bochs GUI00000000000i\[ \] using log file bochs.logNext at t=0(0) \[0x0000fffffff0\] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b ; ea5be000f0<bochs:1> |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Lo que abrirá una ventana totalmente negra como la que hemos visto con QEMU… porque nuestro bootloader no hace absolutamente nada :)

En los capítulos siguientes veremos algo más funcional: un sector de arranque que hace alguna cosa más allá de un bucle infinito saltando a la misma posición de memoria (y no mostrando nada por pantalla).

Referencias:

- Nasm: <https://www.nasm.us/>
- QEMU: <https://www.qemu.org/>
- Bochs: <https://bochs.sourceforge.io/>
- Fichero [simple0.asm](https://drive.google.com/file/d/1iNpf18HR066u9%5FX3rbGNduGiwR-vDA%5F7/view?usp=sharing)