Usando este exemplo, da wikipedia, no qual DrawSquare () chama DrawLine (),
(Observe que este diagrama tem endereços altos na parte inferior e endereços baixos na parte superior.)
Alguém poderia me explicar o que é ebp
e esp
neste contexto?
Pelo que vejo, eu diria que o ponteiro da pilha aponta sempre para o topo da pilha e o ponteiro base para o início da função atual? Ou o que?
editar: quero dizer isso no contexto de programas do Windows
edit2: E como eip
funciona também?
edit3: Eu tenho o seguinte código do MSVC ++:
var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr 8
hPrevInstance= dword ptr 0Ch
lpCmdLine= dword ptr 10h
nShowCmd= dword ptr 14h
Todos eles parecem ser dwords, ocupando 4 bytes cada. Então eu posso ver que há uma diferença de hInstance para var_4 de 4 bytes. O que eles são? Presumo que seja o endereço de retorno, como pode ser visto na foto da wikipedia?
(nota do editor: removemos uma citação longa da resposta de Michael, que não pertence à pergunta, mas uma pergunta de acompanhamento foi editada em):
Isso ocorre porque o fluxo da chamada de função é:
* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals
Minha pergunta (por último, espero!) Agora é: o que é exatamente o que acontece desde o instante em que apareço os argumentos da função que quero chamar até o final do prólogo? Eu quero saber como o ebp, esp evolui durante esses momentos (eu já entendi como o prólogo funciona, eu só quero saber o que está acontecendo depois que eu empurrei os argumentos na pilha e antes do prólogo).
Respostas:
esp
é como você diz que é, o topo da pilha.ebp
é geralmente definido comoesp
no início da função. Os parâmetros de função e variáveis locais são acessados adicionando e subtraindo, respectivamente, um deslocamento constante deebp
. Todas as convenções de chamada x86 definemebp
como sendo preservadas nas chamadas de função.ebp
em si, na verdade, aponta para o ponteiro base do quadro anterior, o que permite caminhar pela pilha em um depurador e exibir outras variáveis locais dos quadros para trabalhar.A maioria dos prólogos de funções se parece com:
Mais tarde, na função, você pode ter um código semelhante (presumindo que as duas variáveis locais sejam 4 bytes)
A otimização de omissão de ponteiro de quadro ou FPO que você pode ativar realmente elimina isso e usa
ebp
como outro registro e acessa locais diretamenteesp
, mas isso torna a depuração um pouco mais difícil, pois o depurador não pode mais acessar diretamente os quadros de pilha de chamadas de função anteriores.EDITAR:
Para sua pergunta atualizada, as duas entradas ausentes na pilha são:
Isso ocorre porque o fluxo da chamada de função é:
hInstance
, etc.)ebp
fonte
ESP é o ponteiro atual da pilha, que será alterado sempre que uma palavra ou endereço for pressionado ou ativado / desativado na pilha. EBP é uma maneira mais conveniente para o compilador acompanhar os parâmetros de uma função e variáveis locais do que usar o ESP diretamente.
Geralmente (e isso pode variar de compilador para compilador), todos os argumentos para uma função que está sendo chamada são empurrados para a pilha pela função de chamada (geralmente na ordem inversa em que são declarados no protótipo da função, mas isso varia) . Em seguida, a função é chamada, que envia o endereço de retorno (EIP) para a pilha.
Após a entrada na função, o antigo valor de EBP é empurrado para a pilha e o EBP é definido como o valor de ESP. Em seguida, o ESP é decrementado (porque a pilha cresce para baixo na memória) para alocar espaço para as variáveis locais e temporários da função. A partir desse momento, durante a execução da função, os argumentos para a função estão localizados na pilha com compensações positivas do EBP (porque foram pressionados antes da chamada da função) e as variáveis locais estão localizadas com compensações negativas do EBP (porque eles foram alocados na pilha após a entrada da função). É por isso que o EBP é chamado de ponteiro de quadro , porque aponta para o centro do quadro de chamada de função .
Ao sair, tudo o que a função precisa fazer é definir ESP para o valor de EBP (que desaloca as variáveis locais da pilha e expõe a entrada EBP na parte superior da pilha) e, em seguida, exibe o antigo valor de EBP da pilha, e a função retorna (inserindo o endereço de retorno no EIP).
Ao retornar para a função de chamada, ele pode incrementar o ESP para remover os argumentos da função que colocou na pilha antes de chamar a outra função. Neste ponto, a pilha está de volta no mesmo estado em que estava antes de chamar a função chamada.
fonte
Você está certo. O ponteiro da pilha aponta para o item superior da pilha e o ponteiro base aponta para o topo "anterior" da pilha antes da chamada da função.
Quando você chama uma função, qualquer variável local será armazenada na pilha e o ponteiro da pilha será incrementado. Quando você retorna da função, todas as variáveis locais na pilha ficam fora do escopo. Você faz isso configurando o ponteiro da pilha de volta no ponteiro base (que era o topo "anterior" antes da chamada da função).
Fazer a alocação de memória dessa maneira é muito , muito rápida e eficiente.
fonte
EDIT: Para uma descrição melhor, consulte Desmontagem / funções x86 e quadros de pilha em um WikiBook sobre montagem x86. Eu tento adicionar algumas informações que você possa estar interessado em usar o Visual Studio.
O armazenamento do EBP do chamador como a primeira variável local é chamado de quadro de pilha padrão e isso pode ser usado para quase todas as convenções de chamada no Windows. Existem diferenças se o chamador ou o destinatário desaloca os parâmetros passados e quais parâmetros são passados nos registradores, mas estes são ortogonais ao problema do quadro de pilha padrão.
Falando sobre programas do Windows, você provavelmente pode usar o Visual Studio para compilar seu código C ++. Esteja ciente de que a Microsoft usa uma otimização chamada Frame Pointer Omission, que torna quase impossível caminhar pela pilha sem usar a biblioteca dbghlp e o arquivo PDB para o executável.
Essa omissão de ponteiro de quadro significa que o compilador não armazena o EBP antigo em um local padrão e usa o registro EBP para outra coisa; portanto, é difícil encontrar o EIP do chamador sem saber quanto espaço as variáveis locais precisam para uma determinada função. É claro que a Microsoft fornece uma API que permite fazer caminhadas pela pilha, mesmo nesse caso, mas pesquisar o banco de dados da tabela de símbolos nos arquivos PDB leva muito tempo para alguns casos de uso.
Para evitar o FPO em suas unidades de compilação, você deve evitar usar / O2 ou adicionar explicitamente / Oy- aos sinalizadores de compilação C ++ em seus projetos. Provavelmente, você vincula o tempo de execução C ou C ++, que usa o FPO na configuração da versão, portanto, será difícil fazer caminhadas pela pilha sem o dbghlp.dll.
fonte
Primeiro, o ponteiro da pilha aponta para a parte inferior da pilha, pois as pilhas x86 são construídas de valores altos de endereço para valores mais baixos de endereço. O ponteiro da pilha é o ponto em que a próxima chamada a ser pressionada (ou chamada) colocará o próximo valor. Sua operação é equivalente à instrução C / C ++:
O ponteiro da base está no topo do quadro atual. ebp geralmente aponta para o seu endereço de retorno. ebp + 4 aponta para o primeiro parâmetro de sua função (ou o valor deste método de classe). ebp-4 aponta para a primeira variável local de sua função, geralmente o valor antigo de ebp, para que você possa restaurar o ponteiro de quadro anterior.
fonte
Muito tempo desde que eu fiz a programação de montagem, mas esse link pode ser útil ...
O processador possui uma coleção de registros que são usados para armazenar dados. Alguns desses são valores diretos, enquanto outros estão apontando para uma área dentro da RAM. Os registros tendem a ser usados para determinadas ações específicas e todo operando em assembly exigirá uma certa quantidade de dados em registros específicos.
O ponteiro da pilha é usado principalmente quando você está chamando outros procedimentos. Com os compiladores modernos, um monte de dados será descartado primeiro na pilha, seguido pelo endereço de retorno, para que o sistema saiba para onde retornar quando for solicitado que retorne. O ponteiro da pilha apontará para o próximo local onde novos dados podem ser enviados para a pilha, onde permanecerão até que sejam exibidos novamente.
Registradores de base ou de segmento apontam apenas para o espaço de endereço de uma grande quantidade de dados. Combinado com um segundo registrador, o ponteiro Base dividirá a memória em grandes blocos enquanto o segundo registrador apontará para um item dentro desse bloco. Os ponteiros de base apontam para a base de blocos de dados.
Lembre-se de que o Assembly é muito específico da CPU. A página à qual vinculei fornece informações sobre os diferentes tipos de CPU.
fonte
Editar Sim, isso está errado. Descreve algo completamente diferente caso alguém esteja interessado :)
Sim, o ponteiro da pilha aponta para o topo da pilha (seja esse o primeiro local de pilha vazio ou o último em que não tenho certeza). O ponteiro base aponta para o local da memória da instrução que está sendo executada. Isso está no nível dos códigos de operação - a instrução mais básica que você pode obter em um computador. Cada código de operação e seus parâmetros são armazenados em um local de memória. Uma linha C ou C ++ ou C # pode ser convertida em um código de operação ou em uma sequência de duas ou mais, dependendo da complexidade. Estes são gravados na memória do programa sequencialmente e executados. Em circunstâncias normais, o ponteiro base é incrementado em uma instrução. Para controle de programa (GOTO, IF, etc), pode ser incrementado várias vezes ou apenas substituído pelo próximo endereço de memória.
Nesse contexto, as funções são armazenadas na memória do programa em um determinado endereço. Quando a função é chamada, certas informações são pressionadas na pilha que permite ao programa descobrir que estava de volta para onde a função foi chamada, bem como os parâmetros para a função, e o endereço da função na memória do programa é empurrado para o diretório ponteiro base. No próximo ciclo do relógio, o computador começa a executar instruções a partir desse endereço de memória. Então, em algum momento, ele retornará ao local da memória APÓS a instrução que chamou a função e continuará a partir daí.
fonte
esp significa "Extended Stack Pointer" ... ebp para "Something Base Pointer" .... eip para "Something Instruction Pointer" ...... O ponteiro da pilha aponta para o endereço de deslocamento do segmento da pilha . O ponteiro base aponta para o endereço de deslocamento do segmento extra. O ponteiro de instruções aponta para o endereço de deslocamento do segmento de código. Agora, sobre os segmentos ... são pequenas divisões de 64 KB da área de memória dos processadores ..... Esse processo é conhecido como Segmentação de Memória. Espero que este post tenha sido útil.
fonte