Traduza a função abaixo para assembly, criando um arquivo foo.s
:
int foo (int x) {
return x+1;
}
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).
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.
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.
Traduza agora a função abaixo para assembler,
int foo (int x) {
return add(x);
}
foo
com
o objdump
.
Note que o código gerado para a instrução call
é
e8 00 00 00 00Nessa 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.
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!