Estou tentando aprender x86. (Arquitetura IA-32) Hoje aprendi sobre o Stack. Isto é o que acho que entendi:
O StackPointer (SP) aponta para o "topo" da pilha (menor endereço) e é armazenado em um registro ESP. O Frame Pointer (FP) permite acessar argumentos e variáveis locais, pois é o "endereço pop" e, portanto, estático, mesmo se o SP se mover mais para dentro da pilha.
int addSome(int arg1, int arg2);
int main(void){
int answer = addSome(1,2);
}
int addSome(int arg1, int arg2){
return arg1 + arg2;
}
A pilha deve ficar assim:
Pilha onde o Frame Pointer mostra "endereço pop"
O que acontece com o FP se houver funções aninhadas? Ele não pode ser movido para cima, pois sua localização será necessária se a subfunção for desempilhada.
Para visualização; adicione esta função, que será chamada dentro de addSome():
int subSome(int arg3, int arg4);
int subSome(int arg3, int arg4){
return arg3 - arg4;
}
Como o FP é manipulado aqui? Como o novo "endereço pop" é conhecido e a localização dos novos argumentos? Pilha onde o Frame Pointer mostra o "endereço pop" da primeira função, a segunda é desconhecida
Meu palpite é que o tamanho da pilha de addSome será salvo em algum lugar para obter a localização do novo "endereço pop" em relação ao FP:
Pilha onde o Frame Pointer tem diferença relativa ao novo "endereço pop"
Mas esse tamanho de pilha teria que ser salvo em algum lugar e, se houver mais funções aninhadas, teria que haver mais lugares para armazenar esses tamanhos, o que acredito que não pode ser verdade.
Primeiro de tudo, em x86, o registrador que usamos para endereçar valores no quadro de pilha é chamado de "ponteiro base", ou
ebp
. Não sei por que você o chama defp
.Em segundo lugar, não precisamos discutir
subSome()
, porque já temos uma segunda função aninhada, pois temos duas funções:main()
eaddSome()
.O prólogo e epílogo padrão de uma função em x86 se parece com isto:
Uma pilha física normal, do mundo real (digamos, uma pilha de cartas) cresce de baixo para cima. Quando você adiciona mais uma carta à pilha, ela vai para o topo da pilha. Não é assim em x86; em x86 a pilha cresce de cima para baixo. A
push <32-bit-operand>
instrução decrementa oesp
registrador em 4 e, em seguida, armazena o operando de 32 bits no endereço de memória apontado pelo novo valor doesp
registrador. Apop <32-bit-operand>
instrução faz o inverso.Além disso, o valor de retorno geralmente não é salvo na pilha. Os detalhes dependem da ABI (Application Binary Interface) em vigor, mas geralmente é retornado no
eax
registrador. Talvez você quisesse dizer o endereço de retorno , que é, de fato, empurrado para a pilha pelacall
instrução e retirado da pilha pelaret
instrução.Então, dentro de
addSome()
, sua pilha ficaria assim:(observe que
top-of-stack
normalmente não será o verdadeiro topo da pilha; haverá mais algumas coisas acima de tudo, empurradas pelo código da biblioteca padrão que invocou seumain()
. Mas a parte mostrada é a parte com a qual nos importamos.)