08 - Variáveis locais
Como visto na expositiva, variáveis locais são armazenadas na pilha. O topo da pilha é armazenado em %rsp
e ela cresce para baixo, ou seja, ao empilhar um dado o valor de %rsp
diminui e ao desempilhar seu valor aumenta. O compilador faz todo o possível para usar somente os registradores, porém em alguns casos é necessário guardar a variável na memória. Isso ocorre, em geral, quando usamos &
para computar o endereço de uma variável. O exemplo mais comum nos códigos que já escrevemos é na leitura de valores usando scanf
.
Exemplo guiado
Funções que guardam variáveis na pilha seguem um padrão facilmente identificável. Primeiro elas subtraem um valor da pilha (0x10
no exemplo abaixo) correspondente ao tamanho total de todas as variáveis usadas. Depois temos várias instruções usando endereços relativos a %rsp
e por fim devolvemos o espaço usado somando 0x10
de volta a %rsp
.
sub $0x10, %rsp
. . . // código da função aqui!
movl 0x8(%rsp),%eax
mov %eax,%edx
addl 0xc(%rsp),%edx
. . . // função continua
add $0x10, %rsp
ret
No exemplo acima, temos duas variáveis locais: 0x8(%rsp)
e 0xc(rsp)
. Cada uma é identificada no código Assembly pelo endereço em que está posicionada na pilha. Logo, todo deslocamento em relação a %rsp
indica um acesso a variável local, sendo que pode ser um acesso de leitura e escrita (usando MOV
, por exemplo) ou da operação endereço de &
(usando LEA
).
Conseguimos identificar que seus tamanhos são int
por duas razões:
- elas aparecem em instruções junto com registradores de 4 bytes (
%eax
e%edx
) - as instruções
movl
eaddl
tem o sufixol
, que indica que os dados tratados tem tamanho 4 bytes. Os sufixos suportados são:b
- 1 bytew
- 2 bytesl
- 4 bytesq
- 8 bytes
Qualquer razão acima é suficiente para identificar os tipos das variáveis locais.
Importante
Novamente, nem toda instrução em Assembly pode ser representada em C. As instruções sub 0x10, %rsp
e add 0x10, %rsp
representam a criação de variáveis locais na pilha e não tem equivalente em C. Simplesmente ignoramos elas e usamos as variáveis locais no código.
Revisão de variáveis globais e strings constantes
Antes de iniciar o próximo exercício vamos revisar como variáveis locais, globais e strings constantes são acessadas em código assembly. A imagem abaixo exemplifica os três casos:
- Variáveis locais: são acessadas com
lea
(para&
- endereço de) oumov
(para leituras e escritas) relativos a%rsp
- Globais e strings constantes: são acessadas usando a notação
0xYY(%rip)
, sendo que o valor0xYY
muda a cada acesso. No caso das strings, o acesso a estes endereços é somente leitura.
O endereçamento relativo a %rip
leva em conta a posição relativa entre a instrução atual e o endereço de memória do dado. Na imagem acima estão destacadas duas instruções lea
que acessam o mesmo dado. Como o %rip
(ponteiro para a próxima instrução) é diferente precisamos de deslocamentos diferentes para acessar o mesmo dado.
Dica: o gdb coloca o endereço calculado ao lado das instruções deste tipo.
Exercise 1
Answer
Não! O registrador %rsp
é especial e sempre guarda o endereço do topo da pilha de chamadas. Nesse espaço de memória guardamos todas variáveis que não podem ser alocadas em um registrador.
Arquivo ex2
O código abaixo (ex2) utiliza variáveis locais.
Dump of assembler code for function func1:
0x05fe <+0>: sub $0x10,%rsp
0x0602 <+4>: movl $0xa,0xc(%rsp)
0x060a <+12>: movl $0xb,0x8(%rsp)
0x0612 <+20>: lea 0xc(%rsp),%rdi
0x0617 <+25>: callq 0x5fa <func2>
0x061c <+30>: addl $0x1,0x8(%rsp)
0x0621 <+35>: lea 0x8(%rsp),%rdi
0x0626 <+40>: callq 0x5fa <func2>
0x062b <+45>: add $0x10,%rsp
0x062f <+49>: retq
Vamos começar analisando as três primeiras linhas do programa.
Exercise 2
Answer
Ao executar sub $0x10,%rsp
criamos um espaço de 16 bytes (0x10
) na pilha. Note que as constantes sempre aparecem em hexadecimal.
Existem vários momentos no código que lemos/escrevemos em endereços relativos a %rsp
. Cada deslocamento representa uma variável local que está armazenada na memória.
Exercise 3
Answer
São duas variáveis int
: uma em 0xc(%rsp)
e outra em 0x8(%rsp)
.
Exercise 4
Answer
Vou utilizar os identificadores e tipos int a
e int b
.
Exercise 5
Answer
Não são aritméticos, portanto representam a operação endereço de &
.
Exercise 6
Answer
Na linha +25
ocorre a chamada func2(&a)
. Já na linha +40
a chamada é func2(&b)
Exercise 7
Arquivo ex3
No exercício anterior vimos como passar variáveis por referência para outras funções. Agora veremos como trabalhar com scanf
. Veja abaixo a função main
do executável ex3
. Abra este arquivo usando o gdb e siga os exercícios.
Dump of assembler code for function main:
0x1149 <+0>: sub $0x18,%rsp
0x114d <+4>: lea 0xc(%rsp),%rsi
0x1152 <+9>: lea 0xeab(%rip),%rdi # 0x2004
0x1159 <+16>: mov $0x0,%eax
0x115e <+21>: callq 0x1040 <__isoc99_scanf@plt>
0x1163 <+26>: cmpl $0x0,0xc(%rsp)
0x1168 <+31>: js 0x1180 <main+55>
0x116a <+33>: lea 0xe9f(%rip),%rdi # 0x2010
0x1171 <+40>: callq 0x1030 <puts@plt>
0x1176 <+45>: mov $0x0,%eax
0x117b <+50>: add $0x18,%rsp
0x117f <+54>: retq
0x1180 <+55>: lea 0xe80(%rip),%rdi # 0x2007
0x1187 <+62>: callq 0x1030 <puts@plt>
0x118c <+67>: jmp 0x1176 <main+45>
Exercise 8
Answer
Estão sendo reservados 24 bytes na pilha. Temos uma variável local int n
.
Exercise 9
Answer
A string de formatação é "%d".
Exercise 10
Answer
É o endereço da variável correspondente a 0xc(%rsp)
Com a chamada do scanf
pronta, vamos analisar o restante do código.
Exercise 12
Answer
Escreve os múltiplos caracteres de uma string, seguido de \n
, na saída padrão
Exercise 13
Answer
São feitas duas chamadas: a primeira é puts("Positivo")
e a segunda é puts("Negativo")
.
Exercise 14
Entrega
Exercícios 4 e 5 da atv6 disponíveis no seu repositório de atividades privado.
Atenção!
Antes de continuar, dê um git pull
em seu repositório de atividades!
Importante
Sempre leia o README.md
disponível na pasta da atividade. Ele tem informações importantes para a execução da atividade, por exemplo caso tenha falha na chamada do make
rode o comando sudo apt install build-essential valgrind kcachegrind libsystemd-dev
no terminal do Linux.