Iniciação ao Assembly x86: Aspectos teóricos

Este tutorial pretende ensinar os procedimentos básicos de programação em linguagem Assembly para processadores x86 em ambientes GNU/Linux.

Para quem não está familiarizado, GNU/Linux é um sistema operativo modelado no UNIX. A parte GNU refere-se ao projecto GNU (GNU’s Not Unix, http://www.gnu.org/), iniciado em 1983 por Richard Stallman, com o objectivo de criar um sistema operativo livre. Em 1991/1992, o projecto GNU já tinha desenvolvido a maior parte das aplicações essenciais para criar um sistema operativo livre, faltando o kernel (núcleo do sistema). Neste momento surge o Linux, um kernel baseado na arquitectura UNIX, desenvolvido por Linus Torvalds, um estudante finlandês. Com a integração dos dois projectos, surge o GNU/Linux, um sistema operativo livre e de código fonte aberto.

O kernel é o componente principal de um sistema operativo, responsável por gerir os recursos do computador e a comunicação entre o hardware e o software. Também funciona como uma camada de abstracção para os componentes/periféricos do computador (por exemplo: a memória, o processador e os dispositivos de I/O). Geralmente o sistema operativo disponibiliza estes recursos através de mecanismos de comunicação entre processos e chamadas de sistema (system calls).

No que toca às linguagens de programação, podemos considerar três categorias:

  • Código máquina
  • Linguagens de baixo nível
  • Linguagens de alto nível

A linguagem Assembly é uma linguagem de baixo nível constituída por um conjunto de mnemónicas e abreviações. Em comparação com código máquina (uma série de números em formato binário), Assembly torna as instruções mais fáceis de lembrar, facilitando a vida ao programador.

O uso da linguagem Assembly já data da década de 1950, sendo nessa altura uma linguagem bastante popular. Actualmente, com a evolução das linguagens de alto nível, é usada maioritariamente no desenvolvimento de drivers, sistemas integrados e na área de reverse engineering (como a maior parte dos programas só estão disponíveis num executável binário ou código máquina, é muito mais fácil traduzi-los para linguagem Assembly do que para linguagens de alto nível—este processo designa-se por disassembly).

O código fonte de um programa em linguagem Assembly está directamente relacionado com a arquitectura específica do processador alvo—ao contrário das linguagens de alto nível, que são geralmente independentes da plataforma, bastando recompilar o código para o executar numa arquitectura diferente.

A linguagem Assembly é traduzida para código máquina através de um programa chamado assembler. Um assembler é diferente de um compilador na medida em que traduz as mnemónicas uma-a-uma para instruções em código máquina, enquanto um compilador traduz as instruções por blocos de código.

Antes de executar o código máquina gerado pelo assembler, temos de fazer a linkagem do executável. Este processo é realizado pelo linker, que basicamente substitui símbolos presentes no código do programa pelos locais concretos onde esses residem. Imaginem que é chamada uma função no código: o linker substitui essa referência pelo local em algum ficheiro onde o código da função se encontra (exemplo: função getline , “módulo iosys – 123 bytes a partir do início”).

Publicado na edição 10 (PDF) da Revista PROGRAMAR.