Primeiros passos com assembly

Toda quinta-feira eu e um grupo de programadores nos reunimos para conversar sobre Golang no Grupo de Estudos de Go mas devíamos mudar o nome do grupo para grupo de estudos de tecnologia porque falamos de tudo remotamente relacionado ao assunto.

Um tema recorrente é programação em assembly. E mais de uma vez falamos de começar a fazer demos e brincadeiras carregadas via setor de boot. Os conhecidos “boot sector games”.

Além de divertido é uma boa forma de ensinar como o computador funciona debaixo do debaixo do capo coisas como velocidade de acesso, cache Lx, e vários outros conceitos são muito abstratos em linguagens de alto nível.

E não tem como os exemplos serem muito grandes, afinal só temos 510 bytes para trabalhar e nosso programa precisa caber todo nesse espaço, o que é bem desafiador.

O limite é 510 bytes porque temos que gastar 2 bytes para a assinatura do setor de boot dos 512 bytes disponíveis.

Preparando o ambiente

Compilador nasm

O nasm é meu compilador favorito para assembly, é multiplataforma, tem uma comunidade bem ativa e a sintaxe é Intel.

Instalando o nasm

No caso do Linux a maioria das distribuições tem algum pacote para o nasm, geralmente um pouco desatualizada mas nunca tive problemas com a versão default do Debian. Hoje em dia eu estou instalando direto dos fontes apenas por gosto pessoal.

Para MacOS o nasm vem junto com o Xcode, e nesse caso eu prefiro deixar a versão fornecida pela Apple.

E finalmente para Windows existe um instalador muito conveniente.

Instalar via código fonte

Baixe o código fonte do site oficial em https://www.nasm.us eu prefiro usar a versão estável.

Descompacte em um diretório e siga as instruções do arquivo INSTALL

Basicamente os seguintes comandos:

./configure
make
sudo make install

Então você pode verificar se a instalação foi bem sucedida com nasm --version

Instalando nasm no Windows

Para instalação em ambiente windows existe um instalador pronto para usar. Vá em https://www.nasm.us/pub/nasm/releasebuilds/ e escolha a versão que preferir, no meu caso win64 e baixe o installer-x64.exe.

Um ponto importante no caso do windows é que o instalador não coloca o diretório do nasm na variável path. Você precisa fazer isso manualmente.

Configurando nasm no neovim

Para informar o neovim que você esta usando o nasm adicione o seguinte comando no init.vim:

"" nasm                                                                        
autocmd BufNewFile,BufRead *.asm set filetype=nasm  

Instalando qemu

Qemu é um emulador muito versátil, alem de emular várias plataformas faz alguns truques interessantes como por exemplo o modo ncurses que redireciona as saídas da tela em modo texto para o terminal permitindo que eu veja telas emuladas mesmo via SSH, a única restrição é que a tela precisa ser modo texto.

Para instalar baixe a versão adequada para seu sistema em https://www.qemu.org/download/

No caso do qemu eu preferi instalar via apt install qemu para o Linux Debian e para Windows usei o instalador para a versão de 64 bits em https://qemu.weilnetz.de/w64/

No caso do Windows assim como foi com o nasm é necessário colocar o qemu na variável de ambiente path.

Testando o ambiente

Agora que nosso ambiente de desenvolvimento esta configurado vamos testar com um famoso “hello world”

Olá mundo

Primeiro crie um arquivo com o nome de boot.asm e entre o conteúdo abaixo.

org 0x7c00          ; set initial addres
    bits 16
                                                                            
    ; cls
    mov ax, 0x3     ; set video mode ah=0 al=3
    int 0x10        ; call bios
    ; print
    xor ax, ax      ; ax=0
    mov es, ax      ; es=ax
    xor dx, dx      ; dx=0
    mov bp, msg     ; set message addres
    mov ax, 0x1301  ; ah=13h print string, al=1 move cursor
    mov bx, 0x000A  ; bh=0 video page 0, bl=0 set atributes
    mov cx, [len]   ; length of string (ignoring attributes)
    int 0x10        ; call bios
    hlt             ; halt
    msg db 'Ola mundo!'
    len dw $-msg
    times  510 - ($-$$) db 0
    dw 0xAA55 ; boot sector magic number

Esse é um exemplo bem básico de setor de boot, na primeira linha avisamos para o compilador o endereço 0x7c00 onde o binário vai ser carregado na memória.

Em seguida definimos o tamanho da palavra básica da maquina para o compilador, como esse é o primeiro código que vai subir no boot da maquina ela ainda esta em 16 bits, modo real

Então usamos um truque antigo, para limpar a tela simplesmente ajustamos o modo de vídeo, e o efeito colateral é limpar a tela. Nosso cls feito em casa.

    ; cls
    mov ax, 0x3     ; set video mode ah=0 al=3
    int 0x10        ; call bios

O próximo passo é um print da string “Ola mundo!”, o print é uma chamada um pouco mais complexa e vamos discutir os detalhes no futuro.

    ; print
    xor ax, ax      ; ax=0
    mov es, ax      ; es=ax
    xor dx, dx      ; dx=0
    mov bp, msg     ; set message addres
    mov ax, 0x1301  ; ah=13h print string, al=1 move cursor
    mov bx, 0x000A  ; bh=0 video page 0, bl=0 set atributes
    mov cx, [len]   ; length of string (ignoring attributes)
    int 0x10        ; call bios

Depois do print queremos que a maquina pare e para isso usamos a instrução hlt.

Daí temos as duas variáveis usadas no exemplo, msg contendo a mensagem e len contendo o tamanho da mensagem, nesse caso usamos uma macro do nasm para calcular o tamanho da string.

    msg db 'Ola mundo!'
    len dw $-msg

A assinatura do setor de boot é 0xAA55, esses precisam ser os dois últimos bytes do setor de boot. Para empurrar esses bytes para a posição certa usamos outra macro times 510 - ($-$$) essa linha pegou o tamanho do programa até agora, subtraiu esse valor de 510 e gerou esse resultado em zeros db 0 dessa forma a assinatura dw 0xAA55 ficou no fim do arquivo exatamente na posição que queremos.

Executando o exemplo

Para compilar o nosso boot.asm com o nasm use o seguinte comando.

nasm -f bin boot.asm -o boot.bin

Para inspecionar o executável podemos usar o utilitário hexdump, muito útil para ver detalhes como o numero mágico da assinatura do setor de boot no final do arquivo.

hexdump boot.bin 
0000000 03b8 cd00 3110 8ec0 31c0 bdd2 7c1b 01b8
0000010 bb13 000a 0e8b 7c25 10cd 4ff4 616c 6d20
0000020 6e75 6f64 0a21 0000 0000 0000 0000 0000
0000030 0000 0000 0000 0000 0000 0000 0000 0000
*
00001f0 0000 0000 0000 0000 0000 0000 0000 aa55

Finalmente para executar o boot no emulador qemu usamos a seguinte linha.

qemu-system-i386 -fda boot.bin

E se você estiver usando uma maquina remota via SSH como eu é só adicionar o parâmetro -curses.

qemu-system-i386 -curses -fda boot.bin

Próximos passos

Mais complicado que programar em assembly limitado a 510 bytes é arrumar tempo para escrever os exemplos.

Nem tudo sera tão limitado, quando surgir a oportunidade pretendo mostrar outras coisas interessantes.

Espero que você goste tanto dessa brincadeira quando eu.

Cesar Gimenes