INF1018 - Laboratório: Código de Máquina
Traduza a função abaixo para assembly, criando um arquivo foo.s.
Atenção: Utilize, em sua tradução, o prólogo e o epílogo abaixo. Mesmo que, neste caso, isso não seja necessário, precisaremos do alinhamento da pilha mais tarde (o prólogo faz esse alinhamento).
Use o comando 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).
Escreva agora um programa em C como descrito a seguir. Esse programa deve criar 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.
Note que o conteúdo do array é o código de máquina de uma função. Nosso objetivo é "chamar" essa função! Para isso, precisamos converter o endereço do array em um endereço de função, armazenar esse endereço em um "ponteiro para uma função", e usar esse ponteiro para realizar a chamada.
Para realizar a conversão para um ponteiro de função adequado à chamada, precisamos declarar um tipo "ponteiro para uma função que recebe um int e retorna um int", e atribuir o endereço do array a uma variável desse tipo:
typedef int (*funcp) (int);
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, lembrando-se de imprimir o valor de i, para confirmar que a função foi chamada.
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 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.
Observe agora o seguinte programa:
int foo(funcp f, int x) {
int i = foo(add, 10);
Repare que a nova função foo recebe um ponteiro para uma função (isto é, um endereço), e chama essa função passando para ela o seu segundo parâmetro. Note que você pode obter, em C, o endereço de uma função usando o seu nome! Execute esse programa, e observe o resultado.
Traduza agora a nova função foo para assembly. Para fazer essa tradução, você vai precisar usar uma instrução assembly que permita chamar a função cujo endereço foi passado como argumento (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:
Obtenha o código de máquina da nova função foo com objdump -d .
Modifique o programa, eliminando a declaração de foo em C, e criando um array codigo com o código de máquina de foo. Novamente, precisamos converter o endereço do array em um endereço de função, armazenar esse endereço em um "ponteiro para uma função", e usar esse ponteiro para realizar a chamada.
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: