O boot loader não pula para o código do kernel

Eu estou escrevendo sistema operacional pequeno – para a prática. Eu comecei com o bootloader.
Eu quero criar pequeno sistema de comando que é executado no modo real de 16 bits (por enquanto).
Eu criei bootloader que redefine a unidade e, em seguida, carrega setor após o bootloader.
O problema é porque depois da function jmp nada acontece.

Eu não estou tentando carregar o próximo setor em 0x7E00 (eu não estou totalmente certo como apontar o endereço usando es: bx, então isso pode ser um problema, acredito que seu endereço: offset), logo após o bootloader.

Este é o 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 

Eu tenho testado este código usando o VirtualBox, mas nada realmente acontece, o erro de leitura não mostra, bem como a mensagem que deve ser impressa.

    Os principais problemas com este código foram:

    1. ES: BX estava apontando para o segmento errado: offset para carregar o kernel
    2. Setor errado estava sendo carregado assim kernel não era o que era esperado

    O primeiro deles estava neste código:

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

    A questão quer carregar o setor do disco para 0x0000:0x7E00 ( ES: BX ). Este código define o ES: BX para 0x7E00:0x0000 que resolve para um endereço físico de 0x7E000 ((0x7E00 < < 4) + 0x0000). Eu acho que a intenção era carregar 0x07E0 em ES, que produziria um endereço físico de 0x7E00 ((0x07E0 < < 4) + 0x0000). Você pode aprender mais sobre 16:16 cálculos de endereçamento de memória aqui . Multiplicar o segmento por 16 é o mesmo que mudar 4 bits.

    O segundo problema no código está aqui:

     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 

    O número para o segundo setor do bloco 512 no disco é 2, não 1. Então, para corrigir o código acima, você precisa definir CL de acordo:

     mov cl,0x2 ;sector number 

    Dicas gerais para o desenvolvimento do bootloader

    Outros problemas que podem atrapalhar o código em execução em vários emuladores, máquinas virtuais e hardware físico real que devem ser endereçados são:

    1. Quando o BIOS pula para o seu código, você não pode confiar nos registros CS , DS , ES , SS , SP com valores válidos ou esperados. Eles devem ser configurados adequadamente quando o seu bootloader for iniciado. Você só pode ser garantido que o seu bootloader será carregado e executado a partir do endereço físico 0x00007c00 e que o número da unidade de boot é carregado no registro DL .
    2. Defina SS: SP para memory que você sabe que não entrará em conflito com a operação de seu próprio código. O BIOS pode ter colocado seu ponteiro de pilha padrão em qualquer lugar no primeiro megabyte de RAM utilizável e endereçável. Não há garantia de onde isso é e se será adequado para o código que você escreve.
    3. O sinalizador de direção usado por lodsb , movsb etc pode ser definido ou desmarcado. Se o sinalizador de direção estiver configurado incorretamente, os registros SI / DI podem ser ajustados na direção errada. Use STD / CLD para ajustá-lo na direção desejada (CLD = forward / STD = backwards). Neste caso, o código assume movimento para frente, portanto, deve-se usar o CLD . Mais sobre isso pode ser encontrado em uma referência de conjunto de instruções
    4. Ao saltar para um kernel, geralmente é uma boa idéia fazer FAR JMP para que ele configure CS: IP para os valores esperados. Isso pode evitar problemas com código do kernel que podem ser quase absolutos perto de JMPs e CALLs no mesmo segmento.
    5. Se o direcionamento do seu gerenciador de partida para o código de 16 bits que funciona nos processadores 8086/8088 (AND higher) evita o uso de registros de 32 bits no código da assembly. Use AX / BX / CX / DX / SI / DI / SP / BP em vez de EAX / EBX / ECX / EDX / ESI / EDI / ESP / EBP . Embora não seja um problema nesta questão, tem sido um problema para outros que procuram ajuda. Um processador de 32 bits pode utilizar registradores de 32 bits no modo real de 16 bits, mas um 8086/8088/80286 não pode, pois eram processadores de 16 bits sem access a registradores estendidos de 32 bits.
    6. Registros de segmento FS e GS foram adicionados a CPUs 80386+. Evite-os se você pretende segmentar 8086/8088/80286.

    Para resolver o primeiro e o segundo item, este código pode ser usado perto do início do gerenciador de boot:

     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 

    Algumas coisas a notar. Quando você altera o valor do registrador SS (neste caso através de um MOV ), o processador deve desligar as interrupções para aquela instrução e mantê-las desligadas até depois das instruções a seguir. Normalmente, você não precisa se preocupar com a desativação de interrupções se atualizar o SS seguido imediatamente por uma atualização do SP . Há um bug em processadores 8088 muito antigos em que isso não era honrado, portanto, se você está mirando nos ambientes mais amplos possíveis, é seguro apostar em desativá-los e reativá-los explicitamente. Se você não pretende nunca trabalhar em um buggy 8088, então as instruções CLI / STI podem ser removidas no código acima. Eu sei sobre esse bug em primeira mão com o trabalho que fiz em meados dos anos 80 no meu PC em casa.

    A segunda coisa a notar é como eu configurei a pilha. Para pessoas novas na assembly de 16 bits do 8088/8086, a pilha pode ser configurada de várias maneiras. Neste caso, configurei o topo da pilha (parte inferior da memory) em 0x8000 ( SS ). Em seguida, defino o ponteiro da pilha ( SP ) para 0 . Quando você empurra algo na pilha no modo real de 16 bits, o processador primeiro diminui o ponteiro da pilha em 2 e, em seguida, coloca um WORD de 16 bits nesse local. Assim, o primeiro push para a pilha seria em 0x0000-2 = 0xFFFE (-2). Você teria então um SS: SP que se parece com 0x8000:0xFFFE . Nesse caso, a pilha é executada de 0 0x8000:0x0000 a 0 0x8000:0x0000 0x8000:0xFFFF .

    Ao lidar com a pilha executando em um 8086 (não se aplica a processadores 80286,80386+), é uma boa idéia definir o ponteiro de pilha ( SP ) para um número par. No 8086 original, se você definir SP como um número ímpar, incorreria em uma penalidade de 4 ciclos de clock para cada access ao espaço de pilha. Como o 8088 tinha um barramento de dados de 8 bits, essa penalidade não existia, mas o carregamento de uma palavra de 16 bits no 8086 levou 4 ciclos de clock, enquanto levou 8 ciclos de clock no 8088 (duas leituras de memory de 8 bits).

    Por fim, se você deseja definir explicitamente CS: IP para que o CS seja configurado corretamente no momento em que o JMP é concluído (em seu kernel), recomenda-se fazer um FAR JMP ( consulte Operações que afetam registros de segmento / FAR Jump ). Na syntax NASM, o JMP ficaria assim:

     jmp 0x07E0:0x0000 

    Alguns montadores (isto é, MASM / MASM32) não têm suporte direto para codificar um FAR Jmp, então uma maneira de fazer isso é manualmente assim:

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

    Se estiver usando o assembler GNU, ele se parecerá com:

     ljmpw $0x07E0,$0x0000