assembly - menuetos - mikeos
¿Cómo ejecutar un programa sin un sistema operativo? (2)
¿Cómo se ejecuta un programa por sí mismo sin un sistema operativo en ejecución? ¿Puede crear programas de ensamblaje que la computadora puede cargar y ejecutar al inicio, por ejemplo, iniciar la computadora desde una unidad flash y ejecuta el programa que está en la CPU?
¿Cómo se ejecuta un programa por sí mismo sin un sistema operativo en ejecución?
Coloca el código binario en un lugar donde el procesador busca después de reiniciar (por ejemplo, la dirección 0 en ARM).
¿Puede crear programas de ensamblaje que la computadora puede cargar y ejecutar al inicio (por ejemplo, iniciar la computadora desde una unidad flash y ejecuta el programa que está en la unidad)?
Respuesta general a la pregunta: se puede hacer. A menudo se denomina "programación bare metal". Para leer desde la unidad flash, desea saber qué es USB y desea tener algún controlador para trabajar con este USB. El programa en este disco también debería estar en algún formato particular. En algún sistema de archivos en particular ... Esto es algo que generalmente hacen los cargadores de arranque. Muchos tableros ARM te permiten hacer algunas de esas cosas. Algunos tienen gestor de arranque para ayudarlo con la configuración básica.
Here puede encontrar un gran tutorial de cómo hacer un sistema operativo básico en Raspberry PI.
Editar: Este artículo y todo el wiki.osdev.org responderá a la mayoría de sus preguntas http://wiki.osdev.org/Introduction
Además, si no desea experimentar directamente en el hardware, puede ejecutarlo como una máquina virtual utilizando hipervisores como qemu. Vea cómo ejecutar "hello world" directamente en el hardware ARM virtualizado here .
Ejemplos ejecutables
Técnicamente, un programa que se ejecuta sin un sistema operativo, es un sistema operativo. Así que veamos cómo crear y ejecutar minúsculos sistemas operativos hello world.
El código de todos los ejemplos a continuación está presente en este repositorio de GitHub . Todo fue probado en Ubuntu 14.04 AMD64 QEMU y hardware real ThinkPad T430.
Sector de arranque
En x86, lo más simple y lo más bajo que puede hacer es crear un sector de inicio maestro (MBR) , que es un tipo de sector de arranque , y luego instalarlo en un disco.
Aquí creamos uno con una sola llamada printf
:
printf ''/364%509s/125/252'' > main.img
sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img
main.img
contiene lo siguiente:
/364
en octal ==0xf4
en hexadecimal: la codificación de una instrucciónhlt
, que le dice a la CPU que deje de funcionar.Por lo tanto, nuestro programa no hará nada: solo comienza y se detiene.
Usamos octal porque
/x
números hexadecimales no están especificados por POSIX.Podríamos obtener esta codificación fácilmente con:
echo hlt > a.asm nasm -f bin a.asm hd a
pero la codificación
0xf4
también está documentada en el manual de Intel, por supuesto.%509s
producen 509 espacios. Necesario para completar el archivo hasta el byte 510./125/252
en octal ==0x55
seguido de0xaa
: bytes mágicos requeridos por el hardware. Deben ser los bytes 511 y 512.Si no está presente, el hardware no lo tratará como un disco de arranque.
Tenga en cuenta que incluso sin hacer nada, algunos caracteres ya están impresos en la pantalla. Esos son impresos por el firmware y sirven para identificar el sistema.
Ejecutar en hardware real
Los emuladores son divertidos, pero el hardware es el verdadero negocio.
Grabe la imagen en una memoria USB (¡destruirá sus datos!):
sudo dd if=main.img of=/dev/sdX
Enchufe el USB en una computadora
encenderlo
dígale que arranque desde el USB.
Esto significa hacer que el firmware seleccione USB antes del disco duro.
Si ese no es el comportamiento predeterminado de su máquina, siga presionando Enter, F12, ESC u otras teclas extrañas después del encendido hasta que obtenga un menú de inicio donde puede seleccionar iniciar desde el USB.
A menudo es posible configurar el orden de búsqueda en esos menús.
Hola Mundo
Ahora que hemos hecho un programa mínimo, pasémonos a un mundo de hola.
La pregunta obvia es: ¿cómo hacer IO? Algunas opciones:
- pregunte al firmware, por ejemplo, BIOS o UEFI, si es necesario para nosotros
- VGA: región de memoria especial que se imprime en la pantalla si está escrita. Se puede usar en modo protegido.
- escribe un controlador y habla directamente con el hardware de la pantalla. Esta es la forma "correcta" de hacerlo: más poderosa, pero más compleja.
Aquí vamos a hacer un ejemplo de BIOS ya que es más simple. Pero tenga en cuenta que no es el método más robusto.
Aquí está el código GAS:
.code16
.global _start
_start:
cli
mov $msg, %si
mov $0x0e, %ah
loop:
lodsb
or %al, %al
jz halt
int $0x10
jmp loop
halt:
hlt
msg:
.asciz "hello world"
.org 510
.word 0xaa55
Además de las instrucciones de ensamblaje de usuario estándar, tenemos:
.code16
: le dice a GAS que.code16
código de 16 bitscli
: deshabilita las interrupciones de software. Esos podrían hacer que el procesador empiece a funcionar nuevamente después delhlt
int $0x10
: hace una llamada BIOS. Esto es lo que imprime los personajes uno por uno..org 510
y.word 0xaa55
: coloque los bytes mágicos al final
Una manera rápida y sucia de compilar esto es con:
as -o main.o main.S
ld --oformat binary -o main.ing -Ttext 0x7C00 main.o
y ejecuta main.img
como antes.
Aquí hay dos banderas importantes:
--oformat binary
:--oformat binary
código de ensamblado binario en bruto, no lo deforma dentro de un archivo ELF como es el caso para los archivos ejecutables regulares de usuario.-Ttext 0x7C00
: necesitamos decirle al vinculadorld
dónde se colocará el código para que pueda acceder a la memoria.En particular, esto se usa durante la fase de reubicación. Lea más sobre esto here .
La mejor forma de compilar es usar un script de enlazador limpio como este . La secuencia de comandos del enlazador también puede colocar los bytes mágicos para nosotros.
Firmware
En verdad, su sector de arranque no es el primer software que se ejecuta en la CPU del sistema.
Lo que realmente se ejecuta primero es el llamado firmware , que es un software:
- hecho por los fabricantes de hardware
- típicamente de fuente cerrada pero probablemente basada en C
- almacenado en la memoria de solo lectura, y por lo tanto, más difícil / imposible de modificar sin el consentimiento del proveedor.
Los firmwares bien conocidos incluyen:
- BIOS : antiguo firmware x86 omnipresente. SeaBIOS es la implementación de código abierto predeterminada utilizada por QEMU.
- UEFI : sucesor del BIOS, mejor estandarizado, pero más capaz e increíblemente hinchado.
- Coreboot : el noble arco cruzado de código abierto
El firmware hace cosas como:
bucle sobre cada disco duro, USB, red, etc. hasta que encuentre algo de arranque.
Cuando ejecutamos QEMU,
-hda
dice quemain.img
es un disco duro conectado al hardware, yhda
es el primero en ser probado, y se usa.cargue los primeros 512 bytes en la dirección de memoria RAM
0x7c00
, ponga allí el RIP de la CPU y déjelo funcionarmostrar cosas como el menú de inicio o las llamadas de impresión del BIOS en la pantalla
El firmware ofrece una funcionalidad tipo OS de la que dependen la mayoría de los OS-es. Por ejemplo, un subconjunto de Python se ha portado para ejecutarse en BIOS / UEFI: https://www.youtube.com/watch?v=bYQ_lq5dcvM
Se puede argumentar que los firmwares son indistinguibles de los sistemas operativos, y que el firmware es la única programación "verdadera" de metal desnudo que uno puede hacer.
Como dice este desarrollador de CoreOS :
La parte difícil
Cuando enciende una PC, las fichas que componen el chipset (northbridge, southbridge y SuperIO) aún no se han inicializado correctamente. Aunque la ROM del BIOS está lo más alejada posible de la CPU, la CPU puede acceder a ella, porque debe ser así, de lo contrario la CPU no tendría instrucciones para ejecutar. Esto no significa que la ROM del BIOS esté completamente mapeada, generalmente no. Pero lo suficiente está mapeado para que el proceso de arranque funcione. Cualquier otro dispositivo, solo olvídalo.
Cuando ejecuta Coreboot en QEMU, puede experimentar con las capas superiores de Coreboot y con cargas útiles, pero QEMU ofrece pocas oportunidades de experimentar con el código de inicio de bajo nivel. Por un lado, la RAM solo funciona desde el principio.
Estado inicial del BIOS posterior
Como muchas otras cosas en el hardware, la estandarización es débil, y una de las cosas con las que no debe confiar es en el estado inicial de los registros cuando el código comienza a ejecutarse después del BIOS.
Así que hágase un favor y use un código de inicialización como el siguiente: https://.com/a/32509555/895245
Los registros como %ds
y %es
tienen efectos secundarios importantes, por lo que debe ponerlos a cero incluso si no los está usando explícitamente.
Tenga en cuenta que algunos emuladores son más agradables que el hardware real y le dan un buen estado inicial. Luego, cuando ejecutas hardware real, todo se rompe.
Multiboot GRUB
Los sectores de arranque son simples, pero no son muy convenientes:
- solo puedes tener un sistema operativo por disco
- el código de carga tiene que ser realmente pequeño y encajar en 512 bytes. Esto podría resolverse con la llamada int 0x13 BIOS .
- tienes que hacer mucho arranque, como pasar al modo protegido
Es por esas razones que GRUB creó un formato de archivo más conveniente llamado multiboot.
Ejemplo de trabajo mínimo: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
Si prepara su sistema operativo como un archivo de arranque múltiple, GRUB puede encontrarlo dentro de un sistema de archivos normal.
Esto es lo que la mayoría de las distribuciones hacen, colocando imágenes de sistema operativo bajo /boot
.
Los archivos de arranque múltiple son básicamente un archivo ELF con un encabezado especial. Están especificados por GRUB en: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
Puede convertir un archivo de arranque múltiple en un disco de inicio con grub-mkrescue
.
El Torito
Formato que se puede grabar en CD: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
También es posible producir una imagen híbrida que funciona en ISO o USB. Esto se puede hacer con grub-mkrescue
( example ), y también lo hace el kernel de Linux en make isoimage
usando isohybrid
.
BRAZO
ARM-land tiene sus propias convenciones (o más falta de convenciones, ya que ARM otorga licencias de IP a los proveedores que lo modifican), pero las ideas generales son las mismas:
- escribes el código en una dirección mágica en la memoria
- haces IO con direcciones mágicas
Algunas diferencias incluyen:
- IO se hace escribiendo a direcciones mágicas directamente, no hay instrucciones de
out
yout
- necesita agregar blobs de fuente cerrada compilados mágicamente proporcionados por proveedores a su imagen. Esas son algo así como BIOS, y eso es algo bueno, ya que hace que la actualización de ese firmware sea más transparente.
Aquí hay unos ejemplos:
- ¿Cómo hacer programas ARM de bare metal y ejecutarlos en QEMU? Interacción UART amigable QEMU
- ¿Cómo ejecutar un programa C sin sistema operativo en la Raspberry Pi? Interacción LED amigable con hardware Raspberry Pi
Recursos
http://wiki.osdev.org es una gran fuente para esos asuntos.
https://electronics.stackexchange.com/questions/223929/c-standard-libraries-on-bare-metal/223931 Para construir software más interesante "bare metal software", es conveniente poder usar C biblioteca estándar. Una posibilidad es usar https://en.wikipedia.org/wiki/Newlib como se menciona en el hilo anterior. Tales implementaciones de biblioteca C de metal desnudo podrían, por ejemplo, redirigir la salida estándar a UART. Sin embargo, creo que debe implementar trozos de syscalls para QEMU: https://balau82.wordpress.com/2010/12/16/using-newlib-in-arm-bare-metal-programs/
https://github.com/scanlime/metalkit es un sistema de compilación bare metal más automatizado / general, que proporciona una pequeña API personalizada
https://github.com/dwelch67/raspberrypi parece una fuente de primera clase para Raspberry Pi (ARM)