INF1018 - Software Básico

Código de Máquina

  1. Traduza a função abaixo para assembly, criando um arquivo foo.s:

    int foo (int x) {
      return x+1;
    }
    

  2. Use gcc -m32 -c foo.s para traduzir seu programa para linguagem de máquina (o gcc vai gerar um arquivo foo.o).

    Veja qual o código de máquina que seu programa gera, usando o comando objdump -d foo.o (a opção -d do objdump faz um "disassembly" do arquivo objeto).

  3. Escreva agora um programa em C como descrito a seguir. Esse programa deve criar um array de bytes (unsigned char codigo[]) preenchido com o código de máquina visto no item anterior. Lembre-se que a saída do objdump mostra os códigos das instruções em hexadecimal. Use esses valores para preencher o array.

    A seguir, o programa deve converter o endereço do array para um endereço de função. Para isso, declare o tipo "ponteiro para uma função que recebe um int e retorna um int", conforme abaixo:

    typedef int (*funcp) (int);
    
    Atribua então o endereço do array a uma variável desse tipo:
    funcp f = (funcp)codigo;
    
    O ponteiro f armazena agora o endereço da função, ou seja, o endereço inicial do código da função, armazenado na memória. Você pode então usar f para chamar essa função como se fosse uma função C, fazendo, por exemplo:
    i = (*f)(10);
    
    Faça isso no seu programa. Deve ser necessário compilar seu programa com

    gcc -m32 -Wall -Wa,--execstack -o seuprograma seuprograma.c

    para permitir a execução do seu código de máquina (sem essa opção, o sistema operacional abortará o seu programa, por tentar executar um código armazenado na área de dados). Execute o programa resultante e verifique a sua saída.

  4. Traduza agora a função abaixo para assembly
    typedef int (*funcp) (int);
    int foo (funcp f, int x) {
      return (*f)(x);
    }
    

  5. Repare que a função foo recebe agora um ponteiro para uma função (isto é, um endereço), e chama essa função passando para ela o seu segundo parâmetro. Você pode obter, em C, o endereço de uma função usando o seu nome:
    typedef int (*funcp) (int);
    int add (int x) {
      return x+1;
    }
    int foo(funcp f, int x) {
      return (*f) (x);
    }
    int main() {
      ...
      i = foo(add, 10);
      ...
    }
    

    Para traduzir a nova foo você vai precisar usar uma instrução assembly que permita chamar essa função f. Uma opção é usar uma variação da instrução call, onde o endereço da função a ser chamada está em um registrador. Por exemplo:

    call *%eax
    
    Observe o código de máquina da nova função foo com objdump -d .
  6. Declare agora no seu arquivo C a função:

    int add (int x) {
      return x+1;
    }
    
    e modifique o programa para ele preencher no array codigo o código de máquina da nova função foo.

    Você vai precisar de um tipo diferente para armazenar o endereço da nova foo: "ponteiro para uma função que recebe um ponteiro para função e um int, e retorna um int". Esse tipo pode ser declarado como abaixo:

    typedef int (*funcp2) (funcp, int);
    

    Execute agora o seu programa, e verifique se tudo funciona corretamente!