09 - Array em Assembly¶
A parte final de nossas atividades com Assembly é entender Arrays. Já estudamos todo o necessário para lidar com eles:
- um array é representado por um apontador para seu primeiro elemento. Já estudamos ponteiros e a escrita em memória com
MOV
. - ao acessar o elemento
i
delong *vec
estamos acessando o endereço&vec[0] + sizeof(long) * i
. A notação de cálculos de endereço de memória faz exatamente isso.
Para iniciar vamos revisar a notação de cálculo de endereços de memória: D(%reg1, %reg2, S)
, onde
D
é um número inteiro que representa um deslocamento constante a partir do endereço guardado em%reg1
%reg1
contém o endereço do primeiro elemento do vetor%reg2
contém o índice do elemento a ser acessadoS
contém o tamanho de cada elemento do vetor e pode ser1, 2, 4
ou8
.
O acesso é feito ao endereço D + %reg1 + %reg2 * S
. Ou seja, primeiro computamos o endereço e depois acessamos a memória no endereço computado.
Exercise
Supondo que %rsi=0x24, %rdi=0x8
, o valor final da expressão (%rsi, %rdi, 4)
é
-
0x32
-
0x48
-
0x56
-
0x44
Answer
Tudo está em hexa. A conta é 36 + 4 * 8 = 68
.
Exercise
Supondo que %rdx=0x44
, o valor da expressão 0x12(%rdx)
é
-
0x56
-
0x44
-
0x52
Answer
Tudo está em hexa. A conta é 18 + 68 = 86 = 0x56
.
Exemplo guiado¶
Dado o array int *vec
(guardado em %rdx
). A atribuição de 5 ao elemento i
(guardado em %ecx
) seria traduzida como
MOVL $0x5, (%rdx, %rcx, 4)
Importante
Lembre-se de que apontadores são tipos de dados com tamanho 8 bytes.
Considerando um vetor short *vec
e que o endereço do primeiro elemento de vec
esteja em %rdi
,
Exercise
Qual a instrução usada para mover o conteúdo de vec[i]
para o registrador %eax
? (supondo que o valor de i
esteja em %esi
)
Answer
mov (%rdi, %rsi, 2), %eax
Exercise
Qual a instrução usada para mover &vec[i]
para o registrador %eax
?
Dica: como você implementava o operador &
com variáveis locais?
Answer
lea (%rdi, %rsi, 2), %rax
Atenção: instrução utilizada apenas de forma pedagógica para diferenciar MOV
e LEA
.
Exercício 1 - função soma
¶
Veja o código abaixo e responda as perguntas.
Dump of assembler code for function soma:
0x0000 <+0>: mov $0x0,%edx
0x0005 <+5>: mov $0x0,%eax
0x000a <+10>: jmp 0x15 <soma+21>
0x000c <+12>: movslq %edx,%rcx
0x000f <+15>: add (%rdi,%rcx,4),%eax
0x0012 <+18>: add $0x1,%edx
0x0015 <+21>: cmp %esi,%edx
0x0017 <+23>: jl 0xc <soma+12>
0x0019 <+25>: repz retq
Exercise
A função acima usa vários registradores. Para facilitar a descoberta de quais são parâmetros da função anote abaixo cada registrador usado e, ao lado, a linha do primeiro uso e se esse uso foi leitura ou escrita.
Answer
Em <soma+0>
, usa-se %edx
para escrita.
Em <soma+5>
, usa-se %eax
para escrita.
Em <soma+12>
, usa-se %rcx
para escrita.
Em <soma+15>
, usa-se %rdi
para leitura.
Em <soma+21>
, usa-se %esi
para leitura.
Exercise
Se o primeiro acesso ao registrador é de escrita então ele provavelmente não é um parâmetro. Com base nisto, escreva abaixo a declaração da função acima.
Answer
int soma(int *vec, int n)
Exercise
Sempre que escrevemos a notação de acesso à memória D(%reg1, %reg2, S)
precisamos usar registradores de 64
bits nos lugares de reg1
e reg2
. Com base nisto, explique qual o uso feito do registrador %edx
e porquê usamos a instrução movslq
na linha c
(ou <soma+12>
).
Answer
Faz o casting com sinal de 4 bytes para 8 bytes. Então o código-fonte original deve utilizar uma variável int i
(4 bytes) para controlar o índice do vetor. Para que a instrução seja realizada, faz-se um casting para long
.
Exercise
Faça uma versão em C do código acima usando somente if-goto
. Escreva, então, ao lado desta versão um código legível em C.
Answer
\\ confira com os professores
Acesso a elementos constantes¶
O acesso a elementos "constantes", como long v[10]; v[5] = 0;
, não é feito usando a notação acima, pois o compilador já sabe em tempo de compilação, qual é o deslocamento necessário para encontrar a posição 6 de v
.
Considerando o exemplo acima, responda.
Exercise
Supondo que v=0x100
, qual o é o endereço de v[5]
?
Answer
Cada long
ocupa 8 bytes, logo o elemento v[5]
está 40 bytes após o início do vetor. Logo, o endereço de v[5]
é 0x100 + 0x028 = 0x128
.
Exercise
Escreva a instrução usada para mover o valor 0
para v[5]
(supondo que o endereço do primeiro elemento esteja em %rdi
).
Answer
mov $0, 0x28(%rdi)
= Início de %rdi
+ 40 bytes.
Importante
O compilador pode (e vai) aplicar diversas otimizações para economizar o máximo possível. Por essa razão, é importante saber calcular os endereços de memória na mão. Assim conseguimos "refazer" o processo que gerou a otimização e entender melhor o código C original.
Exercício 2 - função func_que_recebe_array
¶
Dump of assembler code for function func_que_recebe_array:
0x0000 <+0>: mov 0x4(%rdi),%eax
0x0003 <+3>: add (%rdi),%eax
0x0005 <+5>: cmp 0x8(%rdi),%eax
0x0008 <+8>: setl %al
0x000b <+11>: movzbl %al,%eax
0x000e <+14>: retq
Exercise
Temos acessos à memória relativos ao endereço passado em %rdi
nas linhas 0, 3
e 5
. Isto significa que %rdi
é um ponteiro. Pelos tipos de acessos feitos, você consegue identificar para qual tipo de variável ele aponta?
Answer
Como em na linha <+0>
o vetor está sendo lido em um registrador de 4 bytes, identificamos como int
Exercise
Traduza os acessos de memória feitos nas linhas citadas acima para a notação de acesso a arrays em C.
Answer
Em <+0>
faz-se aux = vec[1]
.
Em <+03>
o acesso é aux = vec[0] + aux
.
Exercise
Com base no respondido acima, faça uma versão em C legível do código assembly acima. Se ajudar, faça uma tradução linha a linha do Assembly e depois torne-a legível.
Answer
int func_que_recebe_array(int *arr) {
long c = arr[0] + arr[1];
return c < arr[2];
}
Exercício 3 - função first_neg
¶
Veja agora o código abaixo e responda.
Dump of assembler code for function first_neg:
0x0000 <+0>: mov $0x0,%eax
0x0005 <+5>: cmp %esi,%eax
0x0007 <+7>: jge 0x17 <first_neg+23>
0x0009 <+9>: movslq %eax,%rdx
0x000c <+12>: cmpl $0x0,(%rdi,%rdx,4)
0x0010 <+16>: js 0x17 <first_neg+23>
0x0012 <+18>: add $0x1,%eax
0x0015 <+21>: jmp 0x5 <first_neg+5>
0x0017 <+23>: repz retq
Exercise
A função acima usa vários registradores. Para facilitar a descoberta de quais são parâmetros da função anote abaixo cada registrador usado e, ao lado, a linha do primeiro uso e se esse uso foi leitura ou escrita.
Answer
Em <first_neg+0>
, usa-se %eax
para escrita.
Em <first_neg+5>
, usa-se %esi
para leitura.
Em <first_neg+12>
, usa-se %rdi
para leitura.
Exercise
Desenhe setas indicando o destino dos pulos no código acima. Você consegue idenfiticar quais estruturas de controle? Entre quais linhas?
Answer
Confira com os professores
Exercise
Faça uma versão em C usando if-goto
do código acima.
Answer
int first_neg(int *vec, int n) {
res = 0;
loop:
if (res>=n) {
goto retorno;
}
if (vec[res] < 0) {
goto retorno;
}
res = res + 1;
goto loop:
retorno:
return res;
}
Exercise
Transforme seu código em uma versão legível.
Answer
int first_neg(int *vec, int n) {
for (int i = 0; i < n; i++) {
if (vec[i] < 0) {
return i;
}
}
}