assembly virtualbox nasm bootloader 16-bit

assembly - El cargador de arranque no salta al código del núcleo



virtualbox nasm (1)

Estoy escribiendo un pequeño sistema operativo, para practicar. Empecé con el gestor de arranque.
Quiero crear un pequeño sistema de comando que se ejecute en modo real de 16 bits (por ahora).
Creé el gestor de arranque que restablece la unidad y luego carga el sector después del gestor de arranque.
El problema es porque después de la función jmp , nada sucede realmente.

No estoy tratando de cargar el siguiente sector en 0x7E00 (no estoy totalmente seguro de cómo apuntar la dirección usando es: bx, por lo que puede ser un problema, creo que es Address: offset), justo después del gestor de arranque.

Este es el código:

; ; SECTOR 0x0 ; ;dl is number of harddrive where is bootloader org 0x7C00 bits 16 ;reset hard drive xor ah,ah int 0x13 ;read sectors clc mov bx,0x7E00 mov es,bx xor bx,bx mov ah,0x02 ;function mov al,0x1 ;sectors to read mov ch,0x0 ;tracks mov cl,0x1 ;sector mov dh,0x0 ;head int 0x13 ;if not readed jmp to error jc error ;jump to 0x7E00 - executed only if loaded jmp 0x7E00 error: mov si,MSGError .loop: lodsb or al,al jz .end mov ah,0x0E int 0x10 jmp .loop .end: hlt MSGError db "Error while booting", 0x0 times 0x1FE - ($ - $$) db 0x0 db 0x55 db 0xAA ; ; SECTOR 0x1 ; jmp printtest ;definitions MSGLoaded db "Execution successful", 0x0 ; ; Print function ; si - message to pring (NEED TO BE FINISHED WITH 0x0) printtest: mov si,MSGLoaded .loop: lodsb or al,al jz .end mov ah,0x0E int 0x10 jmp .loop .end: hlt times 0x400 - ($-$$) db 0x0

He estado probando este código usando VirtualBox pero en realidad no sucede nada. El error de lectura no se muestra, así como el mensaje que debería imprimirse.


Los principales problemas con este código fueron:

  1. ES: BX apuntaba al segmento incorrecto: desplazamiento para cargar el núcleo en
  2. Se estaba cargando un sector incorrecto, por lo que el núcleo no era lo que se esperaba

El primero estaba en este código:

mov bx,0x7E00 mov es,bx xor bx,bx

La pregunta quiere cargar el sector del disco a 0x0000:0x7E00 ( ES: BX ). Este código establece ES: BX en 0x7E00:0x0000 que se resuelve en una dirección física de 0x7E000 ((0x7E00 << 4) + 0x0000). Creo que la intención era cargar 0x07E0 en ES, lo que produciría una dirección física de 0x7E00 ((0x07E0 << 4) + 0x0000). Puede obtener más información sobre los cálculos de direccionamiento de memoria 16:16 here . Multiplicar el segmento por 16 es lo mismo que desplazarlo a la izquierda 4 bits.

El segundo problema en el código está aquí:

mov ah,0x02 ;function mov al,0x1 ;sectors to read mov ch,0x0 ;tracks mov cl,0x2 ;sector number mov dh,0x0 ;head int 0x13

El número para el segundo sector de bloque 512 en el disco es 2, no 1. Entonces, para arreglar el código anterior, debe configurar CL en consecuencia:

mov cl,0x2 ;sector number

Consejos generales para el desarrollo del cargador de arranque

Otros problemas que pueden tropezar con el código en ejecución en varios emuladores, máquinas virtuales y hardware físico real que deben abordarse son:

  1. Cuando el BIOS salta a su código, no puede confiar en que los registros CS , DS , ES , SS , SP tengan valores válidos o esperados. Deben configurarse adecuadamente cuando se inicia el gestor de arranque. Solo se le puede garantizar que su gestor de arranque se cargará y ejecutará desde la dirección física 0x00007c00 y que el número de la unidad de arranque se cargará en el registro DL .
  2. Configure SS: SP en la memoria que sabe que no entrará en conflicto con la operación de su propio código. El BIOS puede haber colocado su puntero de pila predeterminado en cualquier lugar en el primer megabyte de RAM utilizable y direccionable. No hay garantía de dónde es eso y si será adecuado para el código que escriba.
  3. El indicador de dirección utilizado por lodsb , lodsb , etc. podría establecerse o movsb . Si el indicador de dirección está configurado incorrectamente, los registros SI / DI pueden ajustarse en la dirección incorrecta. Use STD / CLD para configurarlo en la dirección que desee (CLD = hacia adelante / STD = hacia atrás). En este caso, el código supone un movimiento hacia adelante, por lo que uno debe usar CLD . Puede encontrar más información sobre esto en una referencia del conjunto de instrucciones
  4. Al saltar a un kernel, generalmente es una buena idea usar FAR JMP para que establezca correctamente CS: IP en los valores esperados. Esto puede evitar problemas con el código del kernel que puede hacer JMPs y CALLs absolutos cerca del mismo segmento.
  5. Si apunta a su cargador de arranque para código de 16 bits que funciona en procesadores 8086/8088 (Y superior), evite el uso de registros de 32 bits en el código de ensamblaje. Utilice AX / BX / CX / DX / SI / DI / SP / BP en lugar de EAX / EBX / ECX / EDX / ESI / EDI / ESP / EBP . Aunque no es un problema en esta pregunta, ha sido un problema para otros que buscan ayuda. Un procesador de 32 bits puede utilizar registros de 32 bits en modo real de 16 bits, pero un 8086/8088/80286 no puede porque eran procesadores de 16 bits sin acceso a registros extendidos de 32 bits.
  6. Los registros de segmento FS y GS se agregaron a 80386+ CPU. Evítalos si tienes la intención de apuntar a 8086/8088/80286.

Para resolver el primer y segundo elemento, este código se puede utilizar cerca del inicio del cargador de arranque:

xor ax,ax ; We want a segment of 0 for DS for this question mov ds,ax ; Set AX to appropriate segment value for your situation mov es,ax ; In this case we''ll default to ES=DS mov bx,0x8000 ; Stack segment can be any usable memory cli ; Disable interrupts to circumvent bug on early 8088 CPUs mov ss,bx ; This places it with the top of the stack @ 0x80000. mov sp,ax ; Set SP=0 so the bottom of stack will be @ 0x8FFFF sti ; Re-enable interrupts cld ; Set the direction flag to be positive direction

Un par de cosas a tener en cuenta. Cuando cambia el valor del registro SS (en este caso a través de un MOV ), se supone que el procesador apaga las interrupciones para esa instrucción y las mantiene apagadas hasta después de la siguiente instrucción. Normalmente no necesita preocuparse por deshabilitar las interrupciones si actualiza SS seguido inmediatamente por una actualización de SP . Hay un error en los procesadores 8088 muy tempranos en el que esto no se respetaba, por lo que si se dirige a los entornos más amplios posibles, es una apuesta segura deshabilitarlos y volver a habilitarlos explícitamente. Si no tiene la intención de trabajar en un buggy 8088, las instrucciones CLI / STI se pueden eliminar en el código anterior. Conozco este error de primera mano con el trabajo que hice a mediados de los 80 en la PC de mi casa.

La segunda cosa a tener en cuenta es cómo configuro la pila. Para las personas nuevas en el ensamblaje de 16 bits 8088/8086, la pila se puede configurar de muchas maneras. En este caso, configuré la parte superior de la pila (la parte más baja de la memoria) en 0x8000 ( SS ). Luego configuré el puntero de pila ( SP ) a 0 . Cuando empuja algo en la pila en modo real de 16 bits, el procesador primero disminuye el puntero de la pila en 2 y luego coloca una PALABRA de 16 bits en esa ubicación. Por lo tanto, el primer impulso a la pila sería en 0x0000-2 = 0xFFFE (-2). Entonces tendría un SS: SP que se parece a 0x8000:0xFFFE . En este caso, la pila se ejecuta desde 0x8000:0x0000 a 0x8000:0xFFFF .

Cuando se trata de la pila que se ejecuta en un 8086 (no se aplica a los procesadores 80286,80386+), es una buena idea establecer el puntero de pila ( SP ) en un número par. En el 8086 original, si configura el SP en un número impar, incurriría en una penalización de 4 ciclos de reloj por cada acceso al espacio de la pila. Como el 8088 tenía un bus de datos de 8 bits, esta penalización no existía, pero cargar una palabra de 16 bits en 8086 tomó 4 ciclos de reloj, mientras que tomó 8 ciclos de reloj en el 8088 (dos lecturas de memoria de 8 bits).

Por último, si desea establecer CS: IP de manera explícita para que CS esté configurado correctamente para cuando el JMP esté completo (en su núcleo), se recomienda hacer un JMP FAR ( consulte Operaciones que afectan a los registros de segmento / Salto FAR ). En la sintaxis NASM, el JMP se vería así:

jmp 0x07E0:0x0000

Algunos ensambladores (es decir, MASM / MASM32) no tienen soporte directo para codificar un FAR Jmp, por lo que una forma de hacerlo es manualmente de esta manera:

db 0x0ea ; Far Jump instruction dw 0x0000 ; Offset dw 0x07E0 ; Segment

Si usa el ensamblador GNU se vería así:

ljmpw $0x07E0,$0x0000