Sistemas de Computação
Lic. Engª
Informática, 1º ano
2013/2014
Docente
responsável: A.J.Proença
Autoavaliação
ISA do IA-32
Ultima Modificação:
28 Mar 2014
|
A |
R |
B |
E |
---|---|---|---|---|
Conhecimentos e competências específicas de Sistemas de Computação |
||||
- Identificar e caracterizar
a operação das instruções mais comuns no IA-32 ( transferência de
informação, operações aritméticas/lógicas, controlo do fluxo de
execução), aplicar técnicas de codificação (de dados escalares e de
estruturas de controlo) de uma HLL imperativa e analisar e interpretar
código em assembly (no papel e no laboratório), |
Ö - - - - |
Ö Ö - - - |
Ö Ö Ö Ö - |
Ö Ö Ö Ö Ö |
A1.
No interior de uma função em C encontra-se a
seguinte instrução:
var_x += elem_y[ind]
Quando essa instrução é compilada num PC (little endian) com "gcc
–S" as únicas linhas de
código que ele gera relacionadas com essa instrução em C, são:
movl 8(%ebp), %ecx
addl -24(%ebp, %ecx,4), %edi
O array elem_y é a única variável local desta função, e que ocupa um espaço de memória contíguo à célula cujo endereço é o actual valor de %ebp; por outro lado, o índice do array é a única variável cujo valor foi passado para esta função como argumento.
O estado do computador imediatamente antes da execução destas 2 instruções é dado pelos seguintes valores presentes em registos e na memória (valores obtidos com vários comandos x do depurador dbg ):
Reg |
Valor |
Reg |
Valor |
%eax | 0x855 | %esi | 0x12 |
%ebx | 0 | %edi | 0x810 |
%ecx | 0xff | %ebp | 0x8401020 |
%edx | 1 | %esp | 0x8401008 |
%eip | 0x812401e |
Endereço / ? | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x812401? | 45 | 04 | 89 | ec | 5d | c3 | 8d | 76 | 00 | 00 | 00 | 55 | 89 | e5 | 8b | 4d |
0x840100? | 00 | 2f | ff | ff | 12 | 20 | 40 | 08 | 02 | 04 | 00 | 00 | fc | ff | ff | ff |
0x840101? | f4 | ff | ff | ff | 00 | 80 | ff | ff | 02 | 02 | 00 | 00 | 00 | 00 | 08 | 00 |
0x840102? | 30 | 10 | 40 | 08 | 02 | 00 | fc | ff | 02 | 00 | 00 | 00 | 12 | 0e | bc | 00 |
a) Relativamente ao array elem_y indique, justificando: (i) a sua dimensão (quantos elementos), (ii) a dimensão de cada um dos elementos do array (quantos bytes), (iii) o valor do 1º elemento do array (em decimal), e (iv) o valor do índice (em decimal), imediatamente antes de executar a instrução em C.
b) Indique o valor da variável
var_x após a
execução destas 2 instruções em assembly. Explicite o raciocínio/cálculo
efectuado.
Nota: a resolução correcta desta alínea apenas é exigida para a classificação
R.
c) Suponha que a variável
var_x se
encontra em memória, cujo endereço é o conteúdo de
%edi
. A instrução de adição (em assembly) poderia ser substituída por
addl
-24(%ebp,%ecx,4),(%edi)
? Justifique.
Nota: a resolução correcta desta alínea apenas é exigida para a classificação
R.
d) Se o processador do PC tivesse
a arquitectura típica dum processador RISC, o compilador não geraria a 2ª linha
de código apresentada no enunciado. Mostre, justificando, que código teria sido
gerado (use a sintaxe do assembler do IA-32).
Nota: a resolução correcta desta alínea apenas é exigida para a classificação
B.
e) Indique, justificando, qual
deverá ser o conteúdo da célula de memória em
0x8124020.
Nota: a resolução correcta desta alínea apenas é exigida para a classificação
B.
f) Face aos elementos
disponibilizados no início deste enunciado, a seguinte afirmação estará
correcta?
"O
array
elem_y
é a única variável local desta
função, e que ocupa um espaço de memória contíguo à célula cujo endereço é o actual
valor de
%ebp;
por outro lado, o índice do array é a única variável cujo valor foi passado para
esta função como argumento."
Nota: a resolução correcta desta alínea, com consulta, está ao alcance da
classificação R; sem consulta, apenas é exigida para a classificação
B.
g) Indique o endereço provável da
instrução que invoca esta função.
Nota: a resolução correcta desta alínea, após resolução da seguinte, pode estar
ao alcance da classificação R (quase B); sem esta dica, é
indispensável para a classificação E.
h) Mostre a estrutura da stack associada a esta função na arquitetura IA-32, após a invocação da função e imediatamente antes da execução do corpo da função, indicando a dimensão e conteúdo de todas as células relevantes, bem como a localização do stack pointer e do base pointer.
R1.
Considere a expressão aritmética com inteiros
a=b[i]-10,
inserida no corpo de uma função em C, em que
a é uma
variável global escalar (cujo apontador foi passado como 1º argumento),
b
é um array global (cujo apontador foi passado como 2º argumento),
i
é a única variável escalar da função.
a) Apresente o código assembly que poderia ser gerado pelo gcc para IA-32, quando encontrasse essa expressão.
b) Mostre a estrutura da stack associada a esta função na arquitectura IA-32, após a invocação da função e imediatamente antes da execução do corpo da função, indicando a dimensão e conteúdo de todas as células relevantes, bem como a localização do stack pointer e do base pointer.
c) Considere agora que o corpo
desta função era apenas constituída por um ciclo
for
contendo no interior aquela expressão seguida da impressão do valor de
a. A variável de controlo do ciclo é
i
(inicializada a 0) e o ciclo percorre todo o array (a dimensão do array, em
termos de nº de elementos, é o 3º argumento passado para esta função).
Mostre o código assembly que o
gcc
iria gerar (com optimização -02),
colocando em comentários o código C correspondente (na versão
goto).
Sugestão: escreva primeiro o código C da função.
d) Supondo que esta era uma
função-folha (i.e., que não invoca nenhuma outra), aponte as diferenças que
deveria encontrar entre a estrutura da stack que mostrou em b) e a que
encontraria numa arquitectura RISC de 32 bits.
Nota: a resolução correcta desta alínea apenas é exigida para a classificação
B.
R2.
Ao analisar código assembly gerado por um
compilador de C, encontraram-se as seguintes linhas referentes a uma estrutura
de controlo:
movl 8(%ebp), %eax
leal -1(%eax), %edx
cmpl 0,%edx
jz .L9
.L10
...
decl %edx
jnz .L10
.L9
Mostre duas possíveis versões do código original em C. Explicite o raciocínio efectuado.
R3.
A compilação de uma função em C, que recebe um
único parâmetro, produziu o seguinte fragmento de código em IA32:
1 movl 8(%ebp),%ebx
2 xorl %ecx,%ecx
3 cmpl %ebx,%ecx
4 jge .L4
5 .L6:
6 incl %ecx
7 cmpl %ebx,%ecx
8 jl .L6
9 .L4:
10 movl %ecx,%eax
11 leave
12 ret
a) Anote pormenorizadamente o
código IA-32 gerado.
Nota: a resolução desta alínea é para o nível A.
b) Considerando que aquele código possui um ciclo implícito, escreva em C a função original.
Quando essa instrução é compilada num PC (little endian) com "gcc
–S" as únicas linhas de
código que ele gera relacionadas com essa instrução em C, são:
movl 8(%ebp), %ecx
addl -24(%ebp, %ecx,4), %edi
O array elem_y é a única variável local desta função, e que ocupa um espaço de memória contíguo à célula cujo endereço é o actual valor de %ebp; por outro lado, o índice do array é a única variável cujo valor foi passado para esta função como argumento.
O estado do computador imediatamente antes da execução destas 2 instruções é dado pelos seguintes valores presentes em registos e na memória (valores obtidos com vários comandos x do depurador dbg ):
Reg |
Valor |
Reg |
Valor |
%eax | 0x855 | %esi | 0x12 |
%ebx | 0 | %edi | 0x810 |
%ecx | 0xff | %ebp | 0x8401020 |
%edx | 1 | %esp | 0x8401008 |
%eip | 0x812401e |
Endereço / ? | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x812401? | 45 | 04 | 89 | ec | 5d | c3 | 8d | 76 | 00 | 00 | 00 | 55 | 89 | e5 | 8b | 4d |
0x840100? | 00 | 2f | ff | ff | 12 | 20 | 40 | 08 | 02 | 04 | 00 | 00 | fc | ff | ff | ff |
0x840101? | f4 | ff | ff | ff | 00 | 80 | ff | ff | 02 | 02 | 00 | 00 | 00 | 00 | 08 | 00 |
0x840102? | 30 | 10 | 40 | 08 | 02 | 00 | fc | ff | 02 | 00 | 00 | 00 | 12 | 0e | bc | 00 |
a) Relativamente ao array elem_y indique, justificando: (i) a sua dimensão (quantos elementos), (ii) a dimensão de cada um dos elementos do array (quantos bytes), (iii) o valor do 1º elemento do array (em decimal), e (iv) o valor do índice (em decimal), imediatamente antes de executar a instrução em C.
Sugestão de resolução
Analisando a instrução em C, o que é que ela indica?
Uma adição do elemento do array
elem_y cujo índice é a variável ind, com a variável var_x.Ainda de acordo com o enunciado, sabe-se que o array
elem_y se encontra na memória: um bloco de células contíguas
desde uma localização inicial até à célula junta àquela que está a ser apontada
pelo conteúdo do registo
%ebp.
E sabe-se tb onde se encontra armazenado (na memória) o valor da varíavel que
representa o índice do array.
Contudo, o enunciado não faz qq referência explícita à localização da variável
var_x;
mas será preciso? Qual deverá ser o local mais apropriado para conter o seu
valor? E o que é que nos indica o código em assembly? Julgo não ser
preciso dizer mais nada...
O código em assembly diz-nos tudo o resto que precisamos de saber...
Com base nesta informação, se ainda não conseguiu resolver, tente de novo...
O código em assembly diz-nos tudo o que precisamos de
saber: que cada elemento do array ocupa 4 células na memória, logo a
dimensão de cada elemento é de 4 bytes (basta ver o
factor de escala no modo de endereçamento à memória na operação de adição), e
que portanto a dimensão do array é de
(24 bytes / 4 bytes =) 6 elementos.
Sabendo onde começa o array (em
Mem[(%ebp)-24]
=
Mem[0x8401020-24]
=
Mem[0x8401020-0x18]
=
Mem[0x8401008]
), é possível retirar da tabela os 4 bytes que constituem o valor do 1º
elemento (em hex
02 04 00 00,
e lendo esse nº como little endian,
0x402
) e calcular o seu valor em decimal (em complemento para 2 é positivo, portanto
basta converter de base 16 ou 2 para base 10; logo, dá 1K+2,
1026 ).
Para saber o valor do índice antes da execução, basta analisar o conteúdo da
memória nos endereços onde ele se encontra (de
0x8401020+8
a
0x8401020+11),
e passá-lo a decimal (sem esquecer a questão de little endian):
2.
Quando essa instrução é compilada num PC (little endian) com "gcc
–S" ... :
Reg |
Valor |
Reg |
Valor |
%eax | 0x855 | %esi | 0x12 |
%ebx | 0 | %edi | 0x810 |
%ecx | 0xff | %ebp | 0x8401020 |
%edx | 1 | %esp | 0x8401008 |
%eip | 0x812401e |
Endereço / ? | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x812401? | 45 | 04 | 89 | ec | 5d | c3 | 8d | 76 | 00 | 00 | 00 | 55 | 89 | e5 | 8b | 4d |
0x840100? | 00 | 2f | ff | ff | 12 | 20 | 40 | 08 | 02 | 04 | 00 | 00 | fc | ff | ff | ff |
0x840101? | f4 | ff | ff | ff | 00 | 80 | ff | ff | 02 | 02 | 00 | 00 | 00 | 00 | 08 | 00 |
0x840102? | 30 | 10 | 40 | 08 | 02 | 00 | fc | ff | 02 | 00 | 00 | 00 | 12 | 0e | bc | 00 |
a) (...)
b) Indique o valor da variável
Sugestão de resolução
Este pb limita-se a pedir a análise da execução de 2 simples instruções: uma de transferência de info (Mem->Reg), outra uma adição em que um dos operandos está em memória.
A 1ª vai buscar o valor da variável de indexação do array (
ind) e coloca-o no registo %ecx. (que já se tinha visto na alínea anterior que era o valor 2, i.e., o índice do 3º elemento do array).A 2ª instrução vai somar esse 3º elemento do array (em memória, nas 4 células que começam em Mem[(%ebp)-24+4*(
%ecx)] = Mem[0x8401020-24+4*2] = Mem[0x8401020-16] = Mem[0x8401020-0x10] = Mem[0x8401010] ), que é o valor 0xfffffff4, com o valor que estava na var_x (no registo %edi).É óbvio que quando se pede o valor de uma variável dum programa em C (do tipo int, como muito provavelmente é o caso), se espera que o resultado seja apresentado em decimal... Neste caso, feitas as contas (de cabeça), o resultado deverá ser... 2052. )
Não se esqueça de indicar sempre as operações que efectuar, para garantir que: (i) se se enganou apenas na aritmética, poder receber a cotação quase integral da resolução do exercício, e (ii) o resultado foi obtido por si, e não estava a "circular" pela sala.
Quando essa instrução é compilada num PC (little endian) com "gcc
–S" ... :
Reg |
Valor |
Reg |
Valor |
%eax | 0x855 | %esi | 0x12 |
%ebx | 0 | %edi | 0x810 |
%ecx | 0xff | %ebp | 0x8401020 |
%edx | 1 | %esp | 0x8401008 |
%eip | 0x812401e |
Endereço / ? | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x812401? | 45 | 04 | 89 | ec | 5d | c3 | 8d | 76 | 00 | 00 | 00 | 55 | 89 | e5 | 8b | 4d |
0x840100? | 00 | 2f | ff | ff | 12 | 20 | 40 | 08 | 02 | 04 | 00 | 00 | fc | ff | ff | ff |
0x840101? | f4 | ff | ff | ff | 00 | 80 | ff | ff | 02 | 02 | 00 | 00 | 00 | 00 | 08 | 00 |
0x840102? | 30 | 10 | 40 | 08 | 02 | 00 | fc | ff | 02 | 00 | 00 | 00 | 12 | 0e | bc | 00 |
a) (...)
b) (...)
c) Suponha que a variável
Sugestão de resolução
A instrução proposta para substituir, como funciona? ...
A arquitectura dos processadores compatíveis com a linha i8086 suporta este tipo de instruções aritméticas, em que os 2 operandos que são explicitados numa única instrução, estão localizados em memória?
Quando essa instrução é compilada num PC (little endian) com "gcc
–S" ... :
movl 8(%ebp), %ecx
addl -24(%ebp, %ecx,4), %edi
Reg |
Valor |
Reg |
Valor |
%eax | 0x855 | %esi | 0x12 |
%ebx | 0 | %edi | 0x810 |
%ecx | 0xff | %ebp | 0x8401020 |
%edx | 1 | %esp | 0x8401008 |
%eip | 0x812401e |
Endereço / ? | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x812401? | 45 | 04 | 89 | ec | 5d | c3 | 8d | 76 | 00 | 00 | 00 | 55 | 89 | e5 | 8b | 4d |
0x840100? | 00 | 2f | ff | ff | 12 | 20 | 40 | 08 | 02 | 04 | 00 | 00 | fc | ff | ff | ff |
0x840101? | f4 | ff | ff | ff | 00 | 80 | ff | ff | 02 | 02 | 00 | 00 | 00 | 00 | 08 | 00 |
0x840102? | 30 | 10 | 40 | 08 | 02 | 00 | fc | ff | 02 | 00 | 00 | 00 | 12 | 0e | bc | 00 |
a) (...)
b) (...)
c) (...)
d) Se o processador do PC tivesse
a arquitectura típica dum processador RISC, o compilador não geraria a 2ª linha
de código apresentada no enunciado. Mostre, justificando, que código teria sido
gerado (use a sintaxe do assembler do IA-32).
Nota: a resolução correcta desta alínea apenas é exigida para a classificação
B.
Sugestão de resolução
Uma das regras básicas de qq arquitectura RISC é que cada
instrução apenas efectua uma operação elementar: ou de acesso à memória - para
ler ou escrever - ou uma operação lógica/aritmética.
Por isso, estas arquitecturas são tb por vezes conhecidas por arquitecturas de
load/store, pois são estas as únicas instruções de acesso à
memória. Assim, numa adição, os operandos fonte têm de estar já em registos, e o
resultado terá de ficar armazenado tb em registo.
E é possível especificar os 3 operandos (2 fonte, e um destino) em qq operação
lógica/aritmética; usando uma sintaxe semelhante à do assembler da Gnu,
uma soma seria escrita como
Com base nestes dados, é possível chegar a uma versão desse código (tente primeiro antes de espreitar...):
multl %ecx, 4, %edx
addl %edx, %ebp, %edx
movl -24(%edx), %edx
addl %edx, %edi, %edi
Quando essa instrução é compilada num PC (little endian) com "gcc
–S" ... :
movl 8(%ebp), %ecx
addl -24(%ebp, %ecx,4), %edi
Reg |
Valor |
Reg |
Valor |
%eax | 0x855 | %esi | 0x12 |
%ebx | 0 | %edi | 0x810 |
%ecx | 0xff | %ebp | 0x8401020 |
%edx | 1 | %esp | 0x8401008 |
%eip | 0x812401e |
Endereço / ? | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x812401? | 45 | 04 | 89 | ec | 5d | c3 | 8d | 76 | 00 | 00 | 00 | 55 | 89 | e5 | 8b | 4d |
0x840100? | 00 | 2f | ff | ff | 12 | 20 | 40 | 08 | 02 | 04 | 00 | 00 | fc | ff | ff | ff |
0x840101? | f4 | ff | ff | ff | 00 | 80 | ff | ff | 02 | 02 | 00 | 00 | 00 | 00 | 08 | 00 |
0x840102? | 30 | 10 | 40 | 08 | 02 | 00 | fc | ff | 02 | 00 | 00 | 00 | 12 | 0e | bc | 00 |
a) (...)
b) (...)
c) (...)
d) (...)
e) Indique, justificando, qual
deverá ser o conteúdo da célula de memória em
0x8124020.
Nota: a resolução correcta desta alínea apenas é exigida para a classificação
B.
Sugestão de resolução
Qual a relação deste endereço com o resto do enunciado do pb?
Não adianta avançar enquanto não responder a esta
questão...
Este endereço é um valor muito próximo do valor do
%eip
imediatamente antes da execução destas 2 instruções; mais concretamente, está a
apontar para o 3º byte localizado após o 1º byte da 1ª instrução.
Portanto, ou aponta para uma célula de memória que ainda contém parte da 1ª
instrução, ou provavelmente é um endereço de memória que contém parte da 2ª
instrução.
Sem ter de consultar o manual de referência da Intel com a
especificação de todas as instruções do instruction set da família IA-32,
será possível chegar a alguma conclusão?
Tente...!
Analisemos essa 1ª instrução: é uma instrução de
mov,
de Mem->Reg. De acordo com os formatos de instrução da Intel, qq instrução deste
tipo em que especificam 2 operandos, são necessários pelo menos 2 bytes:
um contendo o código de operação (opcode), e outro especificando um
registo e um registo/memória.
Se o campo da instrução que especifica o registo/memória indicar uma localização
de memória que necessite de adicionar o valor de um deslocamento (offset),
este terá de vir no(s) byte(s) seguinte(s).
No caso concreto desta instrução, a especificação do endereço de memória - 8(%ebp) - requer o valor de um deslocamento positivo e menor que 127, pelo que é possível especificá-lo usando apenas 1 byte adicional, que será o 3º byte da instrução. E este 3º byte encontra-se precisamente no endereço 0x8124020...
Assim, sendo, qual será o valor que se encontra na memória nesse endereço?...
No interior de uma função em C encontra-se a seguinte instrução:
Quando essa instrução é compilada num PC (little endian) com "gcc
–S" ... :
movl 8(%ebp), %ecx
addl -24(%ebp, %ecx,4), %edi
Reg |
Valor |
Reg |
Valor |
%eax | 0x855 | %esi | 0x12 |
%ebx | 0 | %edi | 0x810 |
%ecx | 0xff | %ebp | 0x8401020 |
%edx | 1 | %esp | 0x8401008 |
%eip | 0x812401e |
Endereço / ? | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x812401? | 45 | 04 | 89 | ec | 5d | c3 | 8d | 76 | 00 | 00 | 00 | 55 | 89 | e5 | 8b | 4d |
0x840100? | 00 | 2f | ff | ff | 12 | 20 | 40 | 08 | 02 | 04 | 00 | 00 | fc | ff | ff | ff |
0x840101? | f4 | ff | ff | ff | 00 | 80 | ff | ff | 02 | 02 | 00 | 00 | 00 | 00 | 08 | 00 |
0x840102? | 30 | 10 | 40 | 08 | 02 | 00 | fc | ff | 02 | 00 | 00 | 00 | 12 | 0e | bc | 00 |
a) (...)
b) (...)
c) (...)
d) (...)
e) (...)
f) Face aos elementos
disponibilizados no início deste enunciado, a seguinte afirmação estará
correcta?
"O
array
elem_y
é a única variável local desta
função, e que ocupa um espaço de memória contíguo à célula cujo endereço é o actual
valor de
%ebp;
por outro lado, o índice do array é a única variável cujo valor foi passado para
esta função como argumento."
Nota: a resolução correcta desta alínea, com consulta, está ao alcance da
classificação R; sem consulta, apenas é exigida para a classificação
B.
Sugestão de resolução
Quando se analisa a estrutura da stack na fase de arranque de uma função, quais as tarefas que normalmente são efectuadas?
PENSE!
E na salvaguarda de registos, que registos competem à função
chamada guardar?
E que registo é usado para escrita na 2ª instrução em assembly deste
enunciado?
E onde vai ser salvaguardado o conteúdo desse registo?
E se ficar aí, o array pode ficar onde se indica no enunciado?...
a) (...)
b) (...)
c) (...)
d) (...)
e) (...)
f) (...)
g) Indique o endereço provável da
instrução que invoca esta função.
Nota: a resolução correcta desta alínea, após resolução da seguinte, pode estar
ao alcance da classificação R (quase B); sem esta dica, é
indispensável para a classificação E.
Sugestão de resolução
Veja no fim da resolução da alínea seguinte.
Quando essa instrução é compilada num PC (little endian) com "gcc
–S" ... :
movl 8(%ebp), %ecx
addl -24(%ebp, %ecx,4), %edi
Reg |
Valor |
Reg |
Valor |
%eax | 0x855 | %esi | 0x12 |
%ebx | 0 | %edi | 0x810 |
%ecx | 0xff | %ebp | 0x8401020 |
%edx | 1 | %esp | 0x8401008 |
%eip | 0x812401e |
Endereço / ? | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x812401? | 45 | 04 | 89 | ec | 5d | c3 | 8d | 76 | 00 | 00 | 00 | 55 | 89 | e5 | 8b | 4d |
0x840100? | 00 | 2f | ff | ff | 12 | 20 | 40 | 08 | 02 | 04 | 00 | 00 | fc | ff | ff | ff |
0x840101? | f4 | ff | ff | ff | 00 | 80 | ff | ff | 02 | 02 | 00 | 00 | 00 | 00 | 08 | 00 |
0x840102? | 30 | 10 | 40 | 08 | 02 | 00 | fc | ff | 02 | 00 | 00 | 00 | 12 | 0e | bc | 00 |
a) (...)
b) (...)
c) (...)
d) (...)
e) (...)
f) (...)
g) (...)
h) Mostre a estrutura da stack
associada a esta função na arquitetura IA-32, após a invocação da função e
imediatamente antes da execução do corpo da função, indicando a dimensão e
conteúdo de todas as células relevantes, bem como a localização do stack pointer
e do base pointer.
Sugestão de resolução
De acordo com o enunciado, se o array é a única variável local da função e o seu índice é a única variável cujo valor foi passado como argumento, então pode-se assumir que
var_x seja a única variável passada como referência, sendo provavelmente o segundo argumento.
Nestas condições, a stack frame associada a esta
função tem a seguinte constituição e estrutura (pela ordem em que ela foi
criada):
- de Mem[(%ebp)+12]
a Mem[(%ebp)+15],
deverá estar o 2º argumento, com 4 bytes; valor: consultar a tabela acima e extrair
0xbc0e12;
- de Mem[(%ebp)+8]
a Mem[(%ebp)+11],
deverá estar o 1º argumento, com 4 bytes; valor: consultar a tabela acima
e extrair
2;
- de Mem[(%ebp)+4]
a Mem[(%ebp)+7],
deverá estar o endereço de retorno, com 4 bytes; valor: consultar a
tabela acima e extrair
0xfffc0002;
- de Mem[(%ebp)]
a Mem[(%ebp)+3],
deverá estar o valor do anterior frame pointer, com 4 bytes; valor: consultar a tabela
acima e extrair 0x8401030;
- de Mem[(%ebp)-24]
a Mem[(%ebp)-1],
deverão estar os 6 inteiros do array, com 24 bytes; valores: consultar a tabela acima e
extrair ...;
Quanto aos valores dos registos %ebp e %esp: no 1º está o valor indicado na tabela do enunciado; o 2º deverá estar a apontar para o topo da stack, que nessa altura estará a apontar para o endereço reservado para o início do 1º elemento do array; ou seja, o %esp registo terá o valor (%ebp)-24 (faça as contas...)
Quanto à alínea anterior:
se o endereço de retorno é 0xfffc0002,
e se este endereço está a apontar para a instrução imediatamente após a
instrução de invocação da função, resta saber qual a dimensão desta instrução;
se apenas 2 bytes, ou se 5 bytes (consoante a distância a que se
encontra a função, se é possível representar essa distância com apenas 8 bits,
ou se serão precisos 32 bits).
Neste caso, 8 bits parecem ser insuficientes. Então o mais provável é a
instrução estar localizada em 0xfffbfffd
(=0xfffc0002-5).
R1.
Considere a expressão aritmética com inteiros
a=b[i]-10,
inserida no corpo de uma função em C, em que
a é uma
variável global escalar (cujo apontador foi passado como 1º argumento),
b
é um array global (cujo apontador foi passado como 2º argumento),
i
é a única variável escalar da função.
a) Apresente o código assembly que poderia ser gerado pelo gcc para IA-32, quando encontrasse essa expressão.
Sugestão de resolução
A operação pretendida é uma subtracção, que no caso do IA-32
será do tipo
subl Src1,Dest,
o que corresponde a
Dest=Dest-Src
.
Isto sugere, desde já, que se carregue o elemento do array da memória
para um registo, e se faça a subtracção com operandos em registos, para
minimizar acessos à memória.
De notar ainda que o índice do array, sendo a única variável escalar da
função, muito provavelmente estará alojada num registo (tb para minimizar
acessos à memória); vamos considerar que é o registo
%ecx.
Sugestão de conversão desta instrução em C para uma sequência de outras que possam ser directamente traduzidas para assembly do IA-32 (notar que a utilização de uma variável temporária, local à função, pressupõe que o compilador irá usar um registo para essa variável):
temp=b[i];
temp-=10;
a=temp;
Vamos ver agora se é possível traduzir cada uma destas instruções directamente para assembly (tente antes de espreitar para a resolução...):
Não é possível... pois é preciso ir tb à memória buscar os endereços do início do array e da variável global a.
movl 12(%ebp), %esi ;
%esi= endereço do início do array b
movl (%esi,%ecx,4), %eax ; %eax= b[i]
subl $-10, %eax
; %eax= b[i]-10
movl 8(%ebp), %esi ;
%esi= endereço da variável a
movl %eax, (%esi)
; a= b[i]-10
Considere a expressão aritmética com inteiros a=b[i]-10, inserida no corpo de uma função em C, em que a é uma variável global escalar (cujo apontador foi passado como 1º argumento), b é um array global (cujo apontador foi passado como 2º argumento), i é a única variável escalar da função.
a)
(...)
b) Mostre a estrutura
da stack associada a esta função na arquitectura IA-32, após a invocação da
função e imediatamente antes da execução do corpo da função, indicando a
dimensão e conteúdo de todas as células relevantes, bem como a localização do
stack pointer e do base pointer.
Sugestão de resolução
Esta questão é idêntica à do problema anterior. A única
complexidade é saber se há ou não registos a salvaguardar nesta função.
Se se considerar que o
Assim, teríamos:
- de Mem[(%ebp)+12]
a Mem[(%ebp)+15],
deverá estar o 2º argumento, o apontador para o array global com 4
bytes;
- de
Mem[(%ebp)+8]
a Mem[(%ebp)+11],
deverá estar o 1º argumento, a variável
a,
do tipo
int,
com 4 bytes;
- de
Mem[(%ebp)+4]
a Mem[(%ebp)+7],
deverá estar o endereço de retorno, com 4 bytes;
- de
Mem[(%ebp)]
a Mem[(%ebp)+3],
deverá estar o valor do anterior frame pointer, com 4 bytes;
- de Mem[(%ebp)-4]
a Mem[(%ebp)-1],
deverá estar o conteúdo que o registo
%esi
tinha no início da execução da função, com 4 bytes.
Quanto aos valores dos registos %ebp e %esp: o 1º aponta para a célula que tem o byte menos significativo do antigo valor do frame pointer, enquanto que o 2º aponta para o byte menos significativo do conteúdo do registo %esi, que foi salvaguardado por esta função.
Considere a expressão aritmética com inteiros a=b[i]-10, inserida no corpo de uma função em C, em que a é uma variável global escalar (cujo apontador foi passado como 1º argumento), b é um array global (cujo apontador foi passado como 2º argumento), i é a única variável escalar da função.
a)
(...)
b) (...)
c) Considere agora que
o corpo desta função era apenas constituída por um ciclo
for
contendo no interior aquela expressão seguida da impressão do valor de
a. A variável de controlo do ciclo é
i
(inicializada a 0) e o ciclo percorre todo o array (a dimensão do array, em
termos de nº de elementos, é o 3º argumento passado para esta função).
Mostre o código assembly que o
gcc
iria gerar (com optimização -02),
colocando em comentários o código C correspondente (na versão
goto).
Sugestão: escreva primeiro o código C da função.
Sugestão de resolução
Vamos considerar o seguinte código C que satisfaz os requisitos do enunciado:
int
func_isa_R1(int *a, int *b, int dim)
{
int i;
for (i = 0; i < dim; i++) {
*a=b[i]-10;
printf
("->%d", *a);
}
return *a;
}
Vamos começar por identificar os registos que iremos usar e respectiva finalidade (objectivo: facilitar a escrita/análise do código e saber quais os registos que deverão ser salvaguardados por esta função enquanto "chamada" e enquanto "chamadora" do printf):
Reg |
Variável |
%eax | a |
%edi | *a |
%edx | *b |
%esi | dim |
%ebx | i |
Vamos agora construir o código do corpo da função e, dentro deste, começar pelo código no interior do ciclo.
Código correspondente a
*a=b[i]-10; :
movl
12(%ebp),%edx
movl
(%edx,%ebx,4),%eax
subl $10,%eax
movl %eax,(%edi)
Código correspondente a printf ("->%d",
*a);:
Código genérico em C associado ao ciclo
for
:
expr_inic ;
cond = expr_test ;
if (! cond)
goto done;
loop:
body_statement
act_expr ;
cond = expr_test ;
if (cond)
goto loop;
done:
Código em C para o ciclo
for neste exercício
concreto (em que a "variável"
cond
foi sempre substituída por
expr_test
), e respectiva codificação em assembly:
i = 0 ;
xorl %ebx,%ebx
if (i >= dim)
cmpl %esi,%ebx
goto done;
jge L6
loop:
L5:
*a=b[i]-10;
; transcrever código feito em cima
printf ("->%d", *a);
; transcrever código feito em cima
i++ ;
incl %ebx
if (i < dim)
cmpl %esi,%ebx
goto loop;
jl L5:
done:
L6:
Está quase concluído o código para o corpo da função. Falta
apenas transferir, no início do corpo, os valores dos argumentos, ainda na
stack, para os registos que se convencionou usar na função (na tabela, em
cima), e preparar, no fim, o valor de retorno da função (para o registo
%eax):
movl 8(%ebp),%edi
movl 16(%ebp),%esi
movl (%edi), %eax
No entanto, uma análise cuidada mostra ainda que:
- há uma instrução dentro do ciclo que está repetidamente a fazer a mesma
coisa (movl
12(%ebp),%edx) sem que aparentemente haja alteração aos
conteúdos desses registos no interior do ciclo; se calhar poderia ser retirada
para antes do início do ciclo;
- por outro lado, esta função antes de chamar o
printf
deveria ter tido o cuidado de salvaguardar alguns registos (quais?),
e depois recuperá-los quando regressasse do
printf.
Mas... um estudo cuidado destes 2 factos poder-nos-á indicar que provavelmente não será preciso fazer nada ao código existente, que ele já estará correcto. Porque será?
Falta construir agora o código do arranque e do término desta função, que contemple os aspectos de gestão da stack frame e a salvaguarda/recuperação de registos:
- arranque:
- término
Considere a expressão aritmética com inteiros a=b[i]-10, inserida no corpo de uma função em C, em que a é uma variável global escalar (cujo apontador foi passado como 1º argumento), b é um array global (cujo apontador foi passado como 2º argumento), i é a única variável escalar da função.
a)
(...)
b) (...)
c) (...)
d) Supondo que esta
era uma função-folha (i.e., que não invoca nenhuma outra), aponte as diferenças
que deveria encontrar entre a estrutura da stack que mostrou em b) e a que
encontraria numa arquitectura RISC de 32 bits.
Nota: a resolução correcta desta alínea apenas é exigida para a classificação
B.
Sugestão de resolução
Estrutura da stack em b):
- de Mem[(%ebp)+12]
a Mem[(%ebp)+15],
deverá estar o 2º argumento, o apontador para o array global com 4
bytes;
- de
Mem[(%ebp)+8]
a Mem[(%ebp)+11],
deverá estar o 1º argumento, a variável
a, do tipo
int, com
4 bytes;
- de
Mem[(%ebp)+4]
a Mem[(%ebp)+7],
deverá estar o endereço de retorno, com 4 bytes;
- de
Mem[(%ebp)]
a Mem[(%ebp)+3],
deverá estar o valor do anterior frame pointer, com 4 bytes;
- de Mem[(%ebp)-4]
a Mem[(%ebp)-1],
deverá estar o conteúdo que o registo
%esi tinha no início da execução da função,
com 4 bytes.
Qual a importância do facto da função ser folha ou ramo?
No IA-32, nenhuma.
Mas num processador RISC, em que normalmente o endereço de retorno é guardado
num registo, (i) numa função não-folha é preciso salvaguardar na stack o
conteúdo do registo que contém o endereço de retorno antes de chamar uma outra
função, e (ii) numa função-folha tal não é necessário.
Assim, quais as diferenças na stack frame do IA-32 e dum
processador RISC? Vejamos:
- argumentos: o RISC tem registos que cheguem para este caso concreto, não
precisa da stack;
- endereço de retorno: como já se viu, o RISC não necessita da stack;
- salvaguarda de registos: os que existem num RISC são suficientes para
que não seja preciso salvaguardar nenhum (dos reservados para a função
chamadora), uma vez que esta função não vai chamar outra;
- variáveis escalares: mesmo que houvesse variáveis escalares na stack
do IA-32, muito provavelmente não seria preciso usar a stack para o RISC,
pois tem registos que chegue; só se houvesse variáveis estruturadas locais...
mas não é este caso;
- actualização do frame pointer: se o RISC não necessita de
stack para a função, para quê o frame pointer?
Em resumo: o processador RISC não precisa de usar a stack para apoio à gestão do contexto desta função!
R2.
Ao analisar código assembly gerado por um
compilador de C, encontraram-se as seguintes linhas referentes a uma estrutura
de controlo:
movl 8(%ebp), %eax
leal -1(%eax), %edx
cmpl 0,%edx
jz .L9
.L10
...
decl %edx
jnz .L10
.L9
Mostre duas possíveis versões do código original em C. Explicite o raciocínio efectuado.
Sugestão de resolução
Uma 1ª análise deste pedaço de código mostra 2 características pertinentes: (i) a existência de uma comparação no ínício à qual está associada uma instrução de salto codicional - cmpcc seguido de jcc, um típico teste de condição - e (ii) uma operação de decremento a anteceder um salto condicional para trás - dec seguido de jcc, uma situação típica de teste de uma condição de saída de um ciclo.
Com base nestas pistas, se ainda não resolveu o pb, tente resolvê-lo antes de continuar...
Uma 2ª análise deste pedaço de código vai permitir identificar que variáveis se estariam a usar no original em C, e que tipo de variáveis seriam:
De notar ainda que esta 2ª instrução é um caso típico de aproveitamento da instrução lea pelo compilador, para efectuar uma operação do tipo addl Src1,Src2,Dest; neste caso concreto, a instrução leal -1(%eax),%edx é usada pelo compilador para substituir esta, que não existe no IA-32: addl -1,%eax,%edx.
Vamos então anotar/comentar o código em assembly:
movl 8(%ebp), %eax ; %eax= arg_1
leal -1(%eax), %edx ; var_loc= arg_1-1
cmpl 0,%edx ; comparar var_loc com 0
jz .L9 ; saltar para o fim se var_loc=0
.L10 ; início de um ciclo
...
decl %edx ; var_loc += -1
jnz .L10 ; repetir o ciclo se var_loc!=0
.L9
A partir daqui temos a solução mais óbvia e directa do possível código original em C:
void exerc_R2 (int arg_1, ...)
int var_loc;
...
var_loc=
arg_1-1;
if
(var_loc=0) goto done;
do {
...
var_loc += -1;
}
while
(var_loc!=0);
done:
Mas esta versão de código original em C não é elegante e é
desaconselhada por usar um
goto.
Uma melhor e mais correcta versão seria:
void exerc_R2 (int arg_1, ...)
int var_loc;
...
var_loc=
arg_1-1;
while
(var_loc!=0)
{
...
var_loc += -1;
}
R3.
A compilação de uma função em C, que recebe um
único parâmetro, produziu o seguinte fragmento de código em IA32:
1 movl 8(%ebp),%ebx
2 xorl %ecx,%ecx
3 cmpl %ebx,%ecx
4 jge .L4
5 .L6:
6 incl %ecx
7 cmpl %ebx,%ecx
8 jl .L6
9 .L4:
10 movl %ecx,%eax
11 leave
12 ret
a) Anote pormenorizadamente o
código IA-32 gerado.
Nota: a resolução desta alínea é para o nível A.
Sugestão de resolução
Esta sugestão de anotar o código vai escrever como comentário/anotação uma descrição que permita visualizar o código em C :
1
movl 8(%ebp),%ebx
; %ebx=arg_1
(int)
2 xorl %ecx,%ecx
; %ecx=0
(int var_loc=0)
3 cmpl %ebx,%ecx
;
A compilação de uma função em C, que recebe um único parâmetro, produziu o seguinte fragmento de código em IA32:
(...)
a) (...)
b) Considerando que aquele código possui um ciclo implícito, escreva em C a
função original.
Sugestão de resolução
Indo do caso mais simples para o mais complexo, esta função poderia ser um ciclo
do...while, while, ou for.A função original em C deveria ser:
void func_R3 (int arg_1)
int var_loc;
...
for
(var_loc=0,
var_loc<arg_1, var_loc++)
{
}
...
return
(var_loc);