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

Was this helpful?

Export as PDF
  1. A base

Procedimentos

Entendendo funções em Assembly

O conceito de um procedimento nada mais é que um pedaço de código que em determinado momento é convocado para ser executado e, logo em seguida, o processador volta a executar as instruções em sequência. Isso nada mais é que uma combinação de dois desvios de fluxo de código, um para a execução do procedimento e outro no fim dele para voltar o fluxo de código para a instrução seguinte a convocação do procedimento. Veja o exemplo em pseudocódigo:

1. Define A para 3
2. Chama o procedimento setarA
3. Compara A e 5
4. Finaliza o código

setarA:
7. Define A para 5
8. Retorna

Seguindo o fluxo de execução do código, a sequência de instruções ficaria assim:

1. Define A para 3
2. Chama o procedimento setarA
7. Define A para 5
8. Retorna
3. Compara A e 5
4. Finaliza o código

Desse jeito se nota que a comparação do passo 3 vai dar positiva porque o valor de A foi setado para 5 dentro do procedimento setarA.

Em Assembly x86 temos duas instruções principais para o uso de procedimentos:

Instrução

Operando

Ação

CALL

endereço

Chama um procedimento no endereço especificado

RET

???

Retorna de um procedimento

A esta altura você já deve ter reparado que nossa função assembly na nossa PoC nada mais é que um procedimento chamado por uma instrução CALL, por isso no final dela temos uma instrução RET.

Na prática o que uma instrução CALL faz é empilhar o endereço da instrução seguinte na stack e, logo em seguida, faz o desvio de fluxo para o endereço especificado assim como um JMP. E a instrução RET basicamente desempilha esse endereço e faz o desvio de fluxo para o mesmo. Um exemplo na nossa PoC:

bits 64

global assembly
assembly:
  mov eax, 3
  call setarA

  ret

setarA:
  mov eax, 5
  ret

#include <stdio.h>

int assembly(void);

int main(void)
{
  printf("Resultado: %d\n", assembly());
  return 0;
}

Na linha 6 damos um call no procedimento setarA na linha 10, este por sua vez altera o valor de EAX antes de retornar. Após o retorno do procedimento a instrução RET na linha 8 é executada, e então retornando também do procedimento assembly.

O que são convenções de chamadas?

É seguindo essa lógica que "milagrosamente" o nosso código em C sabe que o valor em EAX é o valor de retorno da nossa função assembly. Linguagens de alto nível, como C por exemplo, usam um conjunto de regras para definir como uma função deve ser chamada e como ela retorna um valor. Essas regras são a convenção de chamada, em inglês, calling convention.

Na nossa PoC a função assembly retorna uma variável do tipo int que na arquitetura x86 tem o tamanho de 4 bytes e é retornado no registrador EAX. A maioria dos valores serão retornados em alguma parte mapeada de RAX que coincida com o mesmo tamanho do tipo. Exemplos:

Tipo

Tamanho em x86-64

Registrador

char

1 byte

AL

short int

2 bytes

AX

int

4 bytes

EAX

char *

8 bytes

RAX

Por enquanto não vamos ver a convenção de chamada que a linguagem C usa, só estou adiantando isso para que possamos entender melhor como nossa função assembly funciona.

Em um código em C não tente adivinhar o tamanho em bytes de um tipo. Para cada arquitetura diferente que você compilar o código, o tipo pode ter um tamanho diferente. Sempre que precisar do tamanho de um tipo use o operador sizeof.

PreviousSaltosNextSeções e símbolos

Last updated 3 years ago

Was this helpful?