Quando leio sobre assembler, frequentemente encontro pessoas escrevendo que empurram um certo registro do processador e o pop novamente mais tarde para restaurar seu estado anterior.
- Como você pode empurrar um registro? Onde é empurrado? Por que isso é necessário?
- Isso se resume a uma única instrução do processador ou é mais complexa?
assembly
x86
stack
terminology
Emblema Ars
fonte
fonte
b
,w
,l
, ouq
para denotar o tamanho da memória que está sendo manipulado. Ex:pushl %eax
epopl %eax
%eax
sempre tem tamanho de 32 bits.Respostas:
empurrar um valor (não necessariamente armazenado em um registro) significa gravá-lo na pilha.
popping significa restaurar o que quer que esteja no topo da pilha em um registrador. Essas são instruções básicas:
fonte
r/m
, não apenas registrar, então você podepush dword [esi]
. Ou até mesmopop dword [esp]
para carregar e armazenar o mesmo valor de volta no mesmo endereço. ( github.com/HJLebbink/asm-dude/wiki/POP ). Só menciono isso porque você diz "não necessariamente um registro".pop
em uma área da memória:pop [0xdeadbeef]
Aqui está como você empurra um registro. Presumo que estamos falando sobre x86.
Ele é colocado na pilha. O valor do
ESP
registro é decrementado para o tamanho do valor enviado conforme a pilha diminui em sistemas x86.É preciso preservar os valores. O uso geral é
A
push
é uma única instrução em x86, que faz duas coisas internamente.ESP
registro.ESP
registro para o tamanho do valor enviado.fonte
Onde é empurrado?
esp - 4
. Mais precisamente:esp
é subtraído por 4esp
pop
inverte isso.A ABI do System V diz ao Linux para
rsp
apontar para um local de pilha razoável quando o programa começa a ser executado: Qual é o estado de registro padrão quando o programa é iniciado (asm, linux)? que é o que você normalmente deve usar.Como você pode empurrar um registro?
Exemplo mínimo de GNU GAS:
Acima no GitHub com asserções executáveis .
Por que isso é necessário?
É verdade que essas instruções podem ser facilmente implementadas via
mov
,add
esub
.O motivo de sua existência é que essas combinações de instruções são tão frequentes que a Intel decidiu fornecê-las para nós.
A razão pela qual essas combinações são tão frequentes é que elas tornam mais fácil salvar e restaurar os valores dos registros na memória temporariamente para que eles não sejam substituídos.
Para entender o problema, tente compilar algum código C manualmente.
Uma grande dificuldade é decidir onde cada variável será armazenada.
Idealmente, todas as variáveis caberiam em registradores, que é a memória mais rápida para acessar (atualmente cerca de 100 vezes mais rápido que a RAM).
Mas é claro que podemos facilmente ter mais variáveis do que registradores, especialmente para os argumentos de funções aninhadas, então a única solução é escrever na memória.
Poderíamos escrever em qualquer endereço de memória, mas como as variáveis locais e argumentos de chamadas de função e retornos se encaixam em um bom padrão de pilha, que evita a fragmentação da memória , essa é a melhor maneira de lidar com isso. Compare isso com a insanidade de escrever um alocador de heap.
Então, deixamos que os compiladores otimizem a alocação de registros para nós, já que isso é NP completo e uma das partes mais difíceis de escrever um compilador. Esse problema é chamado de alocação de registro e é isomórfico à coloração do gráfico .
Quando o alocador do compilador é forçado a armazenar coisas na memória em vez de apenas registros, isso é conhecido como derramamento .
Isso se resume a uma única instrução do processador ou é mais complexa?
Tudo o que sabemos com certeza é que a Intel documenta
push
umapop
instrução e uma instrução, portanto, são uma instrução nesse sentido.Internamente, ele poderia ser expandido para vários microcódigos, um para modificar
esp
e outro para fazer o IO de memória, e levar vários ciclos.Mas também é possível que um único
push
seja mais rápido do que uma combinação equivalente de outras instruções, uma vez que é mais específico.Isso está principalmente sub (der) documentado:
push
epop
executam uma única microoperação.fonte
push
/pop
decodificar em uops. Graças aos contadores de desempenho, testes experimentais são possíveis, e Agner Fog fez isso e publicou tabelas de instruções . Pentium-M e CPUs posteriores possuem uop únicopush
/pop
graças ao mecanismo de pilha (veja o pdf microarch de Agner). Isso inclui CPUs AMD recentes, graças ao acordo de compartilhamento de patentes Intel / AMD.mov
cargas separadas ). Para variáveis não constantes derramadas, as viagens de ida e volta de encaminhamento à loja são muito mais latentes (um extra de ~ 5c em comparação ao encaminhamento direto e as instruções da loja não são baratas).ocperf.py
script de wrapper para obter nomes simbólicos fáceis para os contadores.Os registros de push e popping estão nos bastidores equivalentes a este:
Observe que esta é a sintaxe At & t x86-64.
Usado como um par, permite salvar um registro na pilha e restaurá-lo posteriormente. Existem outros usos também.
fonte
lea rsp, [rsp±8]
vez deadd
/sub
para emular melhor o efeito depush
/pop
nos sinalizadores.Quase todas as CPUs usam pilha. A pilha do programa é a técnica LIFO com gerenciamento suportado por hardware.
Pilha é a quantidade de memória de programa (RAM) normalmente alocada no topo do heap de memória da CPU e cresce (na instrução PUSH, o ponteiro da pilha é diminuído) na direção oposta. Um termo padrão para inserir na pilha é PUSH e para remover da pilha é POP .
A pilha é gerenciada por meio do registro de CPU pretendido pela pilha, também chamado de ponteiro de pilha, então quando a CPU executa POP ou PUSH, o ponteiro de pilha carrega / armazena um registro ou constante na memória de pilha e o ponteiro de pilha diminui automaticamente xou aumenta de acordo com o número de palavras empurradas ou colocado na (da) pilha.
Através das instruções do montador, podemos armazenar para empilhar:
fonte