O que é o quadro de pilha na assembly?

Qual é a estrutura de um quadro de pilha e como ele é usado ao chamar funções na assembly?

O quadro de pilha x86-32 é criado executando

function_start: push ebp mov ebp, esp 

por isso é acessível através do ebp e parece

 ebp+00 (current_frame) : prev_frame ebp+04 : return_address .... prev_frame : prev_prev_frame prev_frame+04 : prev_return_address 

Há algumas vantagens de usar o ebp para frameworks de pilha pelo design de instruções de assembly, portanto, argumentos e locais geralmente são acessados ​​usando o registrador ebp.

Cada rotina usa uma parte da pilha e nós a chamamos de estrutura de pilha. Embora um programador assembler não seja forçado a seguir o seguinte estilo, é altamente recomendável como boa prática.

O quadro de pilha para cada rotina é dividido em três partes: parâmetros de function, ponteiro de volta para o quadro de pilha anterior e variables ​​locais.

Parte 1: parameters da Função

Essa parte do quadro de pilha de uma rotina é configurada pelo chamador. Usando a instrução ‘push’, o chamador envia os parâmetros para a pilha. Idiomas diferentes podem pressionar os parâmetros em ordens diferentes. C, se bem me lembro, empurra-os da direita para a esquerda. Isto é, se você está ligando …

 foo (a, b, c); 

O chamador irá converter isso para …

 push c push b push a call foo 

À medida que cada item é empurrado para a pilha, a pilha diminui. Ou seja, o registro de ponteiro de pilha é diminuído por quatro (4) bytes (no modo de 32 bits) e o item é copiado para o local de memory apontado pelo registrador de ponteiro de pilha. Note que a instrução ‘call’ implicitamente irá empurrar o endereço de retorno na pilha. A limpeza dos parâmetros será abordada na Parte 5.

Parte 2: Ponteiro do Stackframe Voltar

Neste momento, a instrução de ‘chamada’ foi emitida e agora estamos no início da rotina chamada. Se quisermos acessar nossos parâmetros, podemos acessá-los como …

 [esp + 0] - return address [esp + 4] - parameter 'a' [esp + 8] - parameter 'b' [esp + 12] - parameter 'c' 

No entanto, isso pode ficar desajeitado depois de termos espaço para variables ​​locais e outras coisas. Portanto, usamos um registro de ponteiro de pilha para além do registrador de ponteiro de pilha. No entanto, queremos que o registrador de ponteiro de pilha seja configurado para o nosso quadro atual, e não para a function anterior. Assim, salvamos o antigo na pilha (que modifica os deslocamentos dos parâmetros na pilha) e, em seguida, copiamos o registrador de ponteiro de pilha atual para o registrador de ponteiro de pilha.

 push ebp ; save previous stackbase-pointer register mov ebp, esp ; ebp = esp 

Às vezes você pode ver isso usando apenas a instrução ‘ENTER’.

Parte 3: Criando espaço para variables ​​locais

Variáveis ​​locais são armazenadas na pilha. Como a pilha diminui, nós subtraímos um número de bytes (o suficiente para armazenar nossas variables ​​locais):

sub esp, n_bytes ; n_bytes = number of bytes required for local variables

Parte 4: Colocando tudo junto. Os parâmetros são acessados ​​usando o registro de ponteiro de pilha …

 [ebp + 16] - parameter 'c' [ebp + 12] - parameter 'b' [ebp + 8] - parameter 'a' [ebp + 4] - return address [ebp + 0] - saved stackbase-pointer register 

As variables ​​locais são acessadas usando o registro de ponteiro de pilha …

 [esp + (# - 4)] - top of local variables section [esp + 0] - bottom of local variables section 

Parte 5: Limpeza do Stackframe

Quando deixamos a rotina, o quadro da pilha deve ser limpo.

 mov esp, ebp ; undo the carving of space for the local variables pop ebp ; restore the previous stackbase-pointer register 

Às vezes você pode ver a instrução ‘LEAVE’ substituindo essas duas instruções.

Dependendo do idioma que você estava usando, você poderá ver uma das duas formas da instrução ‘RET’.

 ret ret  

O que for escolhido dependerá da escolha da linguagem (ou estilo que você deseja seguir se estiver escrevendo em assembler). O primeiro caso indica que o chamador é responsável por remover os parâmetros da pilha (com o exemplo foo (a, b, c), ele fará isso via … add esp, 12) e é assim que ‘C’ faz isto. O segundo caso indica que a instrução de retorno irá aparecer # palavras (ou # bytes, não me lembro qual) fora da pilha quando retornar, removendo assim os parâmetros da pilha. Se bem me lembro, este é o estilo usado por Pascal.

É longo, mas espero que isso ajude você a entender melhor os stackframes.

Isso é diferente dependendo do sistema operacional e do idioma usado. Como não há formato geral para a pilha no ASM, a única coisa que a pilha está fazendo no ASM é armazenar o endereço de retorno ao fazer uma sub-rotina de salto. Ao executar um retorno da sub-rotina, o endereço é retirado da pilha e colocado no Contador do Programa (localização da memory onde a próxima instrução de execução da CPU deve ser removida)

Você precisará consultar sua documentação para o compilador que você está usando.

O quadro de pilha x86 pode ser usado por compiladores (dependendo do compilador) para passar parâmetros (ou pointers para parâmetros) e retornar valores. Veja isto