Um pouquinho de assembly faz bem pra vocẽ!

Antes que eu possa começar a analisar códigos em assembly é preciso que eu explique como essa linguagem funciona. Vou tentar fazer isso bem ao estilo do velho “curso” de assembly, só que mais resumido…

Imagine que o processador tenha variáveis internas. Essas variáveis só armazenam valores de 32 bits (cada “variável” armazena um int ou um unsigned int). Existem 7 variáveis (que vou chamar de registradores daqui pra frente) de uso geral: EAX, EBX, ECX, EDX, ESI, EDI e EBP. Embora sejam de uso geral, todos estes podem ter uma função específica em certas instruçṍes, mas isso não interessa, por enquanto.

Eis um exemplo de instruções e registradores:

  mov eax,0      /* move o valor zero para o reg. EAX */
  mov ebx,eax    /* move o conteúdo de EAX para EBX */

Lembre-se que este é o padrão que irei usar. Se vocẽ obter ums listagem de suas rotinas usando a opção -S do gcc, sem especificar o padrão Intel, o código acima ficaria assim:

  movl $0,%eax
  movl %eax,$ebx

Mas só vamos usar essa notação (a da AT&T) quando formos criar nossas próprias rotinas em assembly in-line, no gcc, ok?

Assim como em C, o conteúdo desses registradores podem ser usados como ponteiros. Exemplo:

  mov eax,[ebp]  /* move o conteúdo apontado por EBP para EAX */

Os colchetes são como o operador de ponteiro do C (o asterísco).

E quanto a dados de tamnhos menores? Se só existem registradores de 32 bits então como lidamos com 16 bits (short e unsigned short) e 8 bits (char e unsigned char)? Acontece que os 4 primeiros (EAX, EBX, ECX e EDX) podem ser subdivididos. Os 16 bits inferiores de EAX podem ser refernciados como AX. Os 8 bits superiores de AX é AH e os inferiores AL. Não é possível referenciar somente os 16 bits superiores de EAX diretamente. Se você precisar fazer isso é mais simples fazer:

  shl eax,16	/* Desloca (SHift) EAX 16 bits para a esquerda (Left)! */
  mov [ebx],ax  /* Armazena AX no endereço dado por EBX. */

SI, DI, BP e SP são os 16 bits inferiores de ESI, EDI, EBP e ESP, respectivamente. Para esses registradores não existe equivalente de 8 bits. Só que não tem muita utilidade para esses pseudo-registradores de 16 bits, já que esses caras ai costumam ser usados como ponteiros.

Você entrontrá alguns modificadores quando registradores são usados como ponteiros. No caso acima é óbvio que um valor de 16 bits (o conteúdo de AX) será armazenado em [EBX]. Mas, alguns montadores assembly se confundem com isso. É comum encontrar a instrução acima escrita assim:

  mov word ptr [ebx],ax

Isso só significa que o conteúdo de [EBX] será um WORD (16 bits). Outros modificadores são BYTE (8 bits), DWORD (32 bits), QWORD (64 bits) e TWORD (80 bits). Já que estamos lidando com a arquitetura x86, você só verá QWORD e TWORD em casos especiais (quando lidarmos com os tipos float, double e long double, volto a falar disso!).

Nem só de registradores vive um processador. Precisamos de memória que possa ser referenciada por ponteiros e precisamos de uma pilha. Pilha é uma estrutura onde o primeiro que entra é o último que sai, mas aposto que você já sabia disso.

ESP contém é usado como ponteiro para o início da pilha do seu processo. Nos processadores a pilha cresce para baixo, ou seja, quando colocamos algum valor na pilha nós decrementamos o ponteiro de pilha (ESP). Quando tiramos, incrementamos.

Compiladores C/C++ usam esse recurso para passar parâmetros para funções e para alocar espaço para variáveis locais. Por exemplo: Suponha que tenhamos uma função f() que toma um inteiro como parâmetro:

  void f(int x);

Quando realizamos uma chamada para f(), passando um valor (10, por exemplo), algo assim é gerado:

  mov eax,10
  push eax              /* Empilha o parâmetro de f() */
  call _f
  add esp,4		/* limpa a pilha */

A instrução push “empurra” o conteúdo do registrador EAX para a pilha, a instrução call empurra o endereço da próxima instrução (o endereço do ‘add’ embaixo dele) e salta para o endereço da função f(). Quando a função retorna (e, dai o motivo de call salvar o endereço da próxima instrução – para que o processador saiba para onde saltar “no retorno”!), adicionamos 4 ao ESP para “limpar” a pilha.

O ato de empurrar consiste em subtrair 4 (cada item da pilha tem sempre 4 bytes) de ESP e depois usá-lo como ponteiro para armazenar o dado desejado. A instrução “push eax” acima faz exatamente isso…

Repare que ESP é decrementado ANTES de EAX ser colocado na pilha.

Funções em C não limpam a própria sujeira. É tarefa de quem chamou a função deixar a casa em ordem. E é só por isso que somamos 4 a ESP depois do retorno da função f().

Deixei muita coisa de fora. Não quero sobrecarregar vocẽs. Convenhamos, assembly não é tão complicado assim, é?

Um comentário sobre “Um pouquinho de assembly faz bem pra vocẽ!

Deixe uma resposta

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s