Sistemas de Computação
Mestr. Integr.  Engª Informática, 1º ano
2015/2016
Docente responsável: A.J.Proença

Autoavaliação
ISA do IA-32

Ultima Modificação: 03 Mar 2016
 


ISA do IA-32


Resultados esperados em MIEI/SC: demonstrar que possui
 


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),
- Compreender a estrutura do código e dos contextos gerados por um compilador para suporte à invocação de procedimentos e funções (em HLL) e analisá-la no laboratório,
- Entender e aplicar técnicas de engenharia inversa a código binário (no papel e no laboratório),
- Compreender a organização em memória e métodos de acesso e manipulação de dados estruturados em C, e respectiva análise da codificação para assembly, e
- Analisar comparativamente a implementação de funções em modelos de arquitetura distintos (RISC versus IA-32)


Ö



-

-
-

-

Ö



Ö

-
-

-

Ö


Ö

Ö
Ö

-

Ö


Ö

Ö
Ö

Ö

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.

Proposta de resolução

 

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.

Proposta de resoluçã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.

Proposta de resolução

 

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.

Proposta de resolução

 

Topo...

 

 

 

 

 


Resolução

ISA do IA-32

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.

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 0x8401020+11), e passá-lo a decimal (sem esquecer a questão de little endian): 2.


 

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" ... :
       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) 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.

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.

 

 

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" ... :
       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) 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.

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?

 

 

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" ... :
       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
 
addl   Src1, Src2, Dest .
Adicionalmente, as arquitecturas RISC suportam apenas um leque muito limitado de modos de especificação de um endereço de memória: ou apenas especificam uma localização de memória através da indicação de um registo e de um deslocamento (offset) em relação a esse registo, ou então suportam adicionalmente a possibilidade de especificar um endereço como sendo a soma do conteúdo de 2 registos.

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

 

 

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" ... :
       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:
               var_x += elem_y[ind]

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.

 

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" ... :
       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.
Ainda de acordo com o enunciado, vamos tb assumir que esta função não vai salvaguardar o conteúdo do registo
%edi.

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).
 

 

Regressar...

 


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
gcc geraria o código apresentado na alínea anterior, então saberíamos que esta função necessita de usar o registo %esi, e que portanto este deverá ser salvaguardado na stack, entre o local onde se encontra o antigo frame poiter, e as variáveis locais (que não parece que existam nesta função, pelo menos a usarem a stack).

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 %e
si 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);:
         ??                  ; passagem do 1º argumento para printf
        pushl   %eax
        call    _printf

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:
        pushl   %ebp
        movl    %esp,%ebp
        pushl   %edi
        pushl   %esi
        pushl   %ebx

 - término
        popl    %ebx
        popl    %esi
        popl    %edi
        popl    %ebp
        ret
 

 

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!

 

Regressar...

 


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:

  • a 1ª instrução parece carregar num registo um valor que está na memória num local que já foi referenciado várias vezes noutros exercícios como sendo o local onde normalmente se encontra o 1º argumento passado para a função; como é um valor de 4 bytes, será muito provavelmente um inteiro ou um apontador; as 2 instruções que se lhe seguem claramente indicam que deverá ser um inteiro com sinal; vamos então considerá-lo como o argumento int arg_1;
  • a 2ª instrução carrega um valor no registo %edx; depois é feito um teste sobre ele (imediatamente a seguir) e esse valor é utilizado como variável de controlo do ciclo (mais adiante); isto sugere que o registo %edx é utilizado para conter o valor de uma variável local, um inteiro, a que chamaremos var_loc.

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;
               }
           

Regressar...

 


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          
compara var_loc:arg_1
        4     jge .L4                    
if (var_loc >= arg_1) goto done
        5 .L6:                        
   ;loop:
        6     incl    %ecx               
;  var_loc++
        7     cmpl    %ebx,%ecx          
compara var_loc:arg_1
        8     jl .L6                     
if (var_loc < arg_1) goto loop
        9 .L4:                           
;done:
        10    movl    %ecx,%eax          
;  %eax=var_loc (valor de retorno da func em %eax)
        11    leave                      
;  recupera old_frame_pointer
        12    ret                        
;  return (var_loc)
 

 

 

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.
Atendendo ao teste inicial (antes do ciclo) de uma condição que é a oposta à condição de teste existente dentro do ciclo, e ainda ao facto de existir uma variável de contagem dentro do ciclo que depois é utilizada numa comparação, é razoavelmente óbvio que este é um ciclo
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);
 

 

Regressar...

 

Topo...


Página mantida por aproenca <at> di.uminho.pt
Ultima Modificação: 03 Mar 2016