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/
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 $?
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 $?
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 $?
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.
out dx, al: Escreve o byte contido no registrador AL para a porta de E/S cujo endereço está em DX.in al, dx: Lê um byte da porta de E/S cujo endereço está em DX e o armazena no registrador AL.A porta serial tem vários registradores que são acessados a partir do seu endereço base (0x3F8):
0x3F8 (Base): Data Register (registrador de dados). Para escrita, envia dados. Para leitura, lê dados recebidos.0x3F9 (Base + 1): Interrupt Enable Register (habilita interrupções).0x3FA (Base + 2): Interrupt Identification Register (identificador de interrupção).0x3FB (Base + 3): Line Control Register (controle de linha), onde se configura a velocidade (baud rate), paridade, etc.0x3FD (Base + 5): Line Status Register (estado da linha), que indica se a porta está pronta para receber dados.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.
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 ./progIsso abrirá o GDB. Você verá um prompt (gdb).
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).
(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.
Para ver o código Assembly próximo ao ponto de execução atual, use:
(gdb) disassemble _startUse o comando nexti (next instruction) ou ni para executar a próxima instrução.
(gdb) niIsso executará a instrução corrente.
Para ver o estado de todos os registradores, use:
(gdb) info registersContinue usando ni para avançar e info registers para observar a mudança de estado dos registradores.
Quando terminar de explorar, você pode sair do GDB com o comando quit.
(gdb) quit| 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. |