TE355 - Sistemas Operacionais Embarcados
UFPR DELT Contact

ARQUITETURA DE COMPUTADORES

TE355 - Sistemas Operacionais Embarcados - Prof. Pedroso

1. Introdução à programação em assembler

O objetivo desta página é ilustrar conceitos na área de Arquitetura de Computadores estudados na disciplina de Sistemas Operacionais Embarcados (TE355). Os programas exemplo foram elaborados utilizando a plataforma Linux e o debugador gdb. No entanto, se você não tiver acesso a um sistema Linux, é possível compilar e debugar on line utilizando o site https://www.onlinegdb.com/

1.1. Exemplo 1 - Movimentos de memória

Considere o seguinte programa:

section .data
    ; Declara uma variável na seção de dados.
    ; "my_data" é o rótulo, "dd" define um double-word (32 bits)
    ; e "12345" é o valor inicial.
    my_data dd 12345

section .text
    global _start

_start:
    ; Mover o valor 100 para o registrador EAX
    mov eax, 100

    ; Mover o valor de EAX para o endereço de memória "my_data"
    mov [my_data], eax

    ; Mover o valor da memória "my_data" para o registrador EBX
    mov ebx, [my_data]

    ; --- Saída do programa (syscall para sair) ---
    ; sys_exit é a chamada de sistema 1
    mov eax, 1
    ; Código de retorno 0 (sucesso)
    xor ebx, ebx
    int 0x80

Grave o arquivo como mem.asm.

Para compilar, utilize:

# 1. Compila o arquivo .asm
nasm -f elf32 mem.asm -o mem.o

# 2. Linka o arquivo objeto
ld -m elf_i386 mem.o -o mem

# 3. Executa o programa
./mem

# 4. Verifica o código de saída
echo $?

1.2. Exemplo 2 - Operações com a ULA

Considere o seguinte programa:

section .text
    global _start

_start:
    ; Mover o valor 10 para o registrador EAX
    mov eax, 10
    ; Mover o valor 5 para o registrador EBX
    mov ebx, 5

    ; --- Adição ---
    ; Adicionar o valor de EBX a EAX (EAX = 10 + 5 = 15)
    add eax, ebx

    ; --- Subtração ---
    ; Mover 20 para ECX
    mov ecx, 20
    ; Subtrair 5 de ECX (ECX = 20 - 5 = 15)
    sub ecx, 5

    ; --- Multiplicação ---
    ; Mover 4 para EDX
    mov edx, 4
    ; Multiplicar EAX por EDX. O resultado de 32 bits fica em EAX
    ; EAX = 15 * 4 = 60
    imul edx

    ; --- Saída do programa ---
    mov eax, 1
    xor ebx, ebx
    int 0x80

Grave o arquivo como ula.asm.

Para compilar, utilize:

# 1. Compila o arquivo .asm
nasm -f elf32 ula.asm -o ula.o

# 2. Linka o arquivo objeto
ld -m elf_i386 ula.o -o ula

# 3. Executa o programa
./ula

# 4. Verifica o código de saída
echo $?

1.3. Exemplo 3 - Operações de E/S

Note que não é possível em um sistema operacional moderno realizar operações de E/S diretamente no hardware. O hardware estará em modo protegido! Em vez disso, usamos chamadas de sistema (syscalls) para que o kernel realize a operação em nosso nome.

Considere o seguinte programa:

section .data
    ; A string a ser impressa. "hello" é o rótulo
    msg db 'Hello, World!', 0xa   ; 0xa é o caractere de nova linha (line feed)
    ; O comprimento da string
    len equ $ - msg

section .text
    global _start

_start:
    ; --- Chamada de sistema sys_write ---
    ; sys_write é a chamada de sistema 4
    mov eax, 4
    ; O descritor de arquivo (file descriptor) 1 é a saída padrão (stdout)
    mov ebx, 1
    ; O endereço do buffer (nossa string)
    mov ecx, msg
    ; O comprimento do buffer (o valor que definimos como 'len')
    mov edx, len
    ; Chama o kernel para executar a operação
    int 0x80

    ; --- Saída do programa ---
    mov eax, 1
    xor ebx, ebx
    int 0x80

Grave o arquivo como es.asm.

Para compilar, utilize:

# 1. Compila o arquivo .asm
nasm -f elf32 es.asm -o es.o

# 2. Linka o arquivo objeto
ld -m elf_i386 es.o -o es

# 3. Executa o programa
./es

# 4. Verifica o código de saída
echo $?

1.4. Operações de E/S Diretas (Exemplo Teórico)

O programa a seguir configura a porta serial (baud rate, etc.) e envia um único byte para ela, escrevendo diretamente no hardware. Atualmente, a execução da instrução OUT vai causar um problema, de forma que só é possível executar este programa no modo real do hardware.

Apesar de não ser possível executar o código abaixo diretamente em um ambiente Linux comum, ele é útil para entender a lógica e as instruções que seriam usadas para interagir com a porta serial.

A porta serial COM1 é acessada através de endereços de E/S (I/O ports) específicos. Para a COM1, o endereço base é 0x3F8. Para se comunicar com a porta, usamos as instruções OUT e IN da arquitetura x86.

A porta serial tem vários registradores que são acessados a partir do seu endereço base (0x3F8):

O programa é o seguinte:

section .text
    global _start

_start:
    ; Endereço base da COM1
    mov dx, 0x3F8

    ; --- 1. Configurar a porta serial ---
    ; Habilitar o modo de divisor para configurar o baud rate
    ; Linha de controle no endereço base+3 (0x3FB)
    mov al, 0x80  ; 0x80 = 10000000b (set DLAB)
    mov dx, 0x3FB
    out dx, al

    ; Enviar a taxa de 115200 bps
    ; Divisor para 115200 bps é 1 (115200 / 1 = 115200)
    ; O divisor é um valor de 16 bits, então enviamos em duas partes.
    ; Byte menos significativo (LCR em 0x3F8)
    mov al, 0x01
    mov dx, 0x3F8
    out dx, al

    ; Byte mais significativo (LCR em 0x3F9)
    xor al, al    ; al = 0
    mov dx, 0x3F9
    out dx, al

    ; Resetar o bit DLAB no registrador de controle de linha
    mov al, 0x03 ; 0x03 = 00000011b (8N1)
    mov dx, 0x3FB
    out dx, al

    ; --- 2. Esperar que a porta esteja pronta ---
    .wait_for_transmit:
    mov dx, 0x3FD ; Endereço do Line Status Register
    in al, dx
    ; Verificar se o bit 5 (Transmitter Holding Register Empty) está setado
    test al, 0x20
    jz .wait_for_transmit

    ; --- 3. Enviar um byte para a porta ---
    mov al, 'A'  ; O byte a ser enviado (o caractere 'A')
    mov dx, 0x3F8
    out dx, al

    ; --- Saída do programa (syscall para sair) ---
    mov eax, 1
    xor ebx, ebx
    int 0x80

Ao executar este programa, o SO mostra uma mensagem "Segmentation fault (core dumped)", que é um indicativo genérico de falha. No entanto, o erro real foi uma "Illegal hardware access" produzida pelo hardware, que levou o SO a abortar a execução do programa.


2. Depurar com o GDB (GNU Debugger)

Para depurar os programas gerados, o GDB (GNU Debugger) é a ferramenta padrão e mais poderosa no ambiente Linux. Para usá-lo, você precisa gerar o programa executável com informações de depuração. Depois, basta seguir os passos abaixo para iniciar a depuração.

Inicie o GDB com o executável:

gdb ./prog

Isso abrirá o GDB. Você verá um prompt (gdb).

2.1. Definir um breakpoint no início do programa

O rótulo _start é a entrada do seu programa. Defina um ponto de parada (breakpoint) para que o GDB pause a execução logo no início.

(gdb) b _start

(A forma curta de breakpoint é b).

2.2. Executar o programa

(gdb) run

(A forma curta de run é r). O GDB vai executar o programa até o _start e parar. Ele informará que parou no breakpoint.

2.3. Explorar o código e os registradores

Para ver o código Assembly próximo ao ponto de execução atual, use:

(gdb) disassemble _start

2.4. Executar um passo de cada vez

Use o comando nexti (next instruction) ou ni para executar a próxima instrução.

(gdb) ni

Isso executará a instrução corrente.

Para ver o estado de todos os registradores, use:

(gdb) info registers

2.5. Continuar executando e observando

Continue usando ni para avançar e info registers para observar a mudança de estado dos registradores.

2.6. Sair do GDB

Quando terminar de explorar, você pode sair do GDB com o comando quit.

(gdb) quit

2.7. Resumo dos comandos úteis do GDB

Comando Descrição
b <label> Define um breakpoint no rótulo (ex: b _start).
r Executa o programa até o primeiro breakpoint.
ni Executa a próxima instrução de Assembly.
si Executa a próxima instrução, "entrando" em chamadas de função (útil para sub-rotinas).
i r Mostra o valor de todos os registradores.
x/<n>x <endereço> Examina a memória no endereço. n é o número de bytes a exibir.
c Continua a execução até o próximo breakpoint.
q Sai do GDB.