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 -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. Declare um array de bytes global (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.


    Atenção: o array codigo deverá estar armazenado na área de dados global para que os exercícios deste laboratório funcionem corretamente. Não declare o array como uma variável local!

    Seu programa deve converter o endereço do array para um endereço de função. Para isso, declare o tipo "ponteiro para função recebendo int e retornando int", conforme abaixo:

    typedef int (*funcp) (int x);
    
    Na sua função main, atribua 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, fazendo, por exemplo:
    i = (*f)(10);
    
    Faça isso no seu programa, imprimindo a seguir o valor da variável i para poder verificar se o seu código de máquina foi realmente executado. Deve ser necessário compilar seu programa com

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

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

  4. Traduza agora a função abaixo para assembler,

    int foo (int x) {
      return add(x);
    }
    

  5. Observe o código de máquina da nova função foo com o objdump. Note que o código gerado para a instrução call é
    e8 00 00 00 00 
    
    Nessa instrução, o byte e8 representa o código de call, e os quatro bytes seguintes, o deslocamento da função chamada em relação à instrução seguinte ao call (isto é, a diferença entre os dois endereços).

    Esse deslocamento é armazenado em little endian, e pode ser um valor negativo ou positivo, dependendo do endereço da função chamada ser "menor" ou "maior" que o da instrução seguinte ao call.

    Note que o deslocamento NÃO está correto no arquivo .o, pois o endereço da função chamada somente será conhecido no passo de linkedição (a função não está definida no módulo). O montador então preenche a posição do deslocamento com um valor "default", que será depois "corrigido" pelo linkeditor.

  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.

    Como você pode obter o endereço de add "programaticamente" (isto é, usando o operador &), seu programa C pode calcular qual deve ser o deslocamento correspondente à chamada de add no seu código de máquina. Lembre-se que o seu código está armazenado no espaço reservado para o array e, portanto, o endereço do início desse código é o próprio endereço do array...

    Modifique agora seu programa C, fazendo com que ele "corrija" a instrução call, preenchendo os quatro bytes após o opcode e8 com o deslocamento calculado. Lembre-se que esse valor deve estar em little-endian!

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