Aprendendo Assembly
Doar para o autor
  • Introdução
  • Conteúdo
  • Como usar este livro
  • A base
    • Noção geral da arquitetura
    • Modos de operação
    • Sintaxe
    • Registradores de propósito geral
    • Endereçamento
    • Pilha
    • Saltos
    • Procedimentos
    • Seções e símbolos
    • Instruções assembly x86
    • Instruções do NASM
    • Pré-processador do NASM
    • Syscall no Linux
    • Olá mundo no Linux
    • Revisão
  • Aprofundando em Assembly
    • Registradores de segmento
    • CALL e RET
    • Position-independent executable
    • Atributos
    • Prefixos
    • Flags do processador
    • Instruções condicionais
    • Programando no MS-DOS
    • Interrupções de software e exceções
    • Procedimentos do BIOS
    • Usando instruções da FPU
    • Entendendo SSE
      • Instruções de movimentação de dados
      • Instruções aritméticas
      • Instruções lógicas e de comparação
      • Instruções com inteiros 128-bit
      • Instruções de conversão
  • Programando junto com C
    • Sintaxe do GAS
    • Convenção de chamada da System V ABI
    • Convenções de chamada no Windows
    • Variáveis em C
    • Funções em C
    • Ambiente hosted
    • Ambiente freestanding
    • Inline Assembly no GCC
    • Instruções intrínsecas
  • Depuração de código
    • Entendendo os depuradores
    • Depurando com o GDB
    • Depurando com o Dosbox
  • Apêndices
    • Código de máquina
      • Formato das instruções
      • Atributos e prefixos
      • Immediate
      • Displacement
      • ModR/M e SIB
      • Opcode
      • Prefixo REX
      • Codificação dos registradores
  • Metadados
    • TO DO
    • Referências
Powered by GitBook
On this page
  • Interrupt Descriptor Table
  • Exception
  • IDT em Real Mode
  • Sinais

Was this helpful?

Export as PDF
  1. Aprofundando em Assembly

Interrupções de software e exceções

Interrupções e exceções sendo entendidas na prática.

Uma interrupção é um sinal enviado para o processador solicitando a atenção dele para a execução de outro código. Ele para o que está executando agora, executa este determinado código da interrupção e depois volta a executar o código que estava executando antes. Esse sinal é geralmente enviado por um hardware externo para a CPU, cujo o mesmo é chamado de IRQ — Interrupt Request — que significa "pedido de interrupção".

Enquanto a interrupção de software é executada de maneira muito semelhante a uma chamada de procedimento por far call. Ela é basicamente uma interrupção que é executada pelo software rodando na CPU, daí o nome.

No caso de interrupções de softwares sendo disparadas em um processo executando sob um sistema operacional, o código executado da interrupção é definido pelo próprio sistema operacional e está fora da memória do processo. Portanto há uma troca de contexto onde a tarefa momentaneamente fica suspensa enquanto a interrupção não finaliza.

Interrupt Descriptor Table

O código que é executado quando uma interrupção é disparada se chama handler e o endereço do mesmo é definido na IDT — Interrupt Descriptor Table. Essa tabela nada mais é que uma sequência de valores indicando o offset e segmento do código à ser executado. É uma array onde cada elemento contém essas duas informações. Poderíamos representar em C da seguinte forma:

// Em 16-bit

struct elem {
  uint16_t offset;
  uint16_t segment;
}

struct elem idt[256];

Ou seja o número que identifica a interrupção nada mais é que o índice a ser lido no vetor.

Exception

Provavelmente você já ouviu falar em exception. A exception nada mais é que uma interrupção e tem o seu handler definido na IDT. Por exemplo quando você comete o erro clássico de tentar acessar uma região de memória inválida ou sem permissões adequadas em C, você compila o código e recebe a clássica mensagem segmentation fault.

Nesse caso a exceção que foi disparada pelo processador se chama General Protection e pode ser referida pelo mnemônico #GP, seu índice na tabela é 13.

Essa exceção é disparada quando há um problema na referência de memória ou qualquer proteção à memória que foi violada. Como por exemplo ao tentar escrever em um segmento de memória que não tem permissão para escrita.

Um sistema operacional configura uma exceção da mesma forma que configura uma interrupção, modificando a IDT para apontar para o código que ele quer que execute. Nesse caso o índice 13 precisaria ser modificado.

IDT em Real Mode

A instrução int imm8 é usada para disparar interrupções de software/exceções. Bastando simplesmente passar o índice da interrupção como operando.

Vamos ver na prática a configuração de uma interrupção em 16-bit. Para isso vamos usar o MS-DOS para que fique mais simples.

A IDT está localizada no endereço 0 em real mode, por isso podemos configurar para acessar o segmento zero e assim o offset seria o índice de cada elemento da IDT. O que precisamos fazer é acessar o índice que queremos modificar na IDT, depois é só jogar o offset e segmento do procedimento que queremos que seja executado. Em 16-bit isso acontece de uma maneira muito mais simples do que em protected mode, por isso é ideal para entender na prática.

Eis o código:

int.asm
bits 16
org  0x100

VADDR equ 0xb800

; ID, segmento, offset
%macro setint 3
  mov bx, (%1) * 4
  mov word [es:bx], %3
  mov word [es:bx + 2], %2
%endmacro


; -- Main -- ;
mov ax, 0
mov es, ax

setint 0x66, cs, int_putchar

mov al, 'A'
mov ah, 0x0B
int 0x66

mov ah, 0x0C
int 0x66

ret

; -- Interrupção -- ;
int_cursor: dw 0

; Argumentos:
;   AL    Caractere
;   AH    Atributo
int_putchar:
  push es
  mov bx, VADDR
  mov es, bx

  mov di, [int_cursor]
  mov word [es:di], ax

  add word [int_cursor], 2
  pop es
  iret

Para compilar e testar usando o Dosbox:

$ nasm int.asm -o int.com
$ dosbox int.com

A interrupção simplesmente escreve os caracteres na parte superior esquerda da tela.

Note que a interrupção retorna usando a instrução iret ao invés de ret. Em 16-bit a única diferença nessa instrução é que ela também desempilha o registrador de flags, que é empilhado pelo processador ao disparar a interrupção/exceção.

Perceba que é unicamente um código de exemplo. Essa não é uma maneira segura de se configurar uma interrupção tendo em vista que seu handler está na memória do .com que, após finalizar sua execução, poderá ser sobrescrita por outro programa executado posteriormente.

Os depuradores modificam a instrução original colocando a instrução que dispara a exceção de breakpoint. Depois tratam o sinal enviado para o processo, restauram a instrução original e continuam seu trabalho.

O breakpoint nada mais é que uma exceção que é disparada por uma instrução. Podemos usar int 0x03 (CD 03 em código de máquina) para fazer isso porém essa instrução tem 2 bytes de tamanho e não é muito apropriada para um depurador usar. Por isso existe a instrução int3 que dispara #BP explicitamente e tem somente 1 byte de tamanho (opcode 0xCC).

int.asm
bits 16
org  0x100

; ID, segmento, offset
%macro setint 3
  mov bx, (%1) * 4
  mov word [es:bx], %3
  mov word [es:bx + 2], %2
%endmacro


; -- Main -- ;

xor ax, ax
mov es, ax

setint 0x03, cs, break

int3
int3

ret

; -- Breakpoint -- ;

break:
  mov ah, 0x0E
  mov al, 'X'
  int 0x10
  iret

Sinais

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

void segfault(int signum)
{
  fputs("Tá pegando fogo bixo!\n", stderr);
  exit(signum);
}

// Esse código também funciona no Windows.
int main(void)
{
  char *desastre = NULL;
  struct sigaction action = {
      .sa_handler = segfault,
  };

  sigaction(SIGSEGV, &action, NULL);

  strcpy(desastre, "Eita!");

  puts("Tchau mundo!");
  return 0;
}
PreviousProgramando no MS-DOSNextProcedimentos do BIOS

Last updated 2 years ago

Was this helpful?

No Linux basicamente o que o sistema faz é criar um handler que trata a exceção e manda um para o processo. Esse sinal o processo pode configurar como ele quer tratar, mas por padrão o processo escreve uma mensagem no terminal e finaliza.

Mais um exemplo mas dessa vez configurando a exceção #BP de índice 3. Se você já usou um , ou pelo menos tem uma noção à respeito, sabe que "breakpoint" é um ponto no código onde o depurador faz uma parada e te permite analisar o programa enquanto ele fica em pausa.

Repare que a cada disparo de int3 executou o código do nosso procedimento break. Esse por sua vez imprimiu o caractere 'X' na tela do Dosbox usando a interrupção 0x10 que será explicada no .

Só para deixar mais claro o que falei sobre que são enviados para o processo quando uma exception é disparada, aqui um código em C de exemplo:

Mais detalhes sobre os sinais serão descritos no tópico .

depurador
próximo tópico
Entendendo os depuradores
sinal
os sinais
Intel Developer's Manuals - volume 1, capítulo 6