Jonathan403 Postado Janeiro 21, 2021 em 18:39 Compartilhar Postado Janeiro 21, 2021 em 18:39 Eu tava estudando sobre esses 2 registradores, e eu não entendi muito bem sobre eles. O ESP aponta para o topo da pilha, e o EBP pelo q eu tava pesquisando, aponta pra base da pilha. Mas como assim ? O EBP aponta pro endereço de memória onde começa o stack frame ? É isso ? E o ESP vai apontar pro topo do stack frame ? Link para o comentário Compartilhar em outros sites More sharing options...
fredericopissarra Postado Janeiro 21, 2021 em 20:00 Compartilhar Postado Janeiro 21, 2021 em 20:00 Nos modos de operação real (16 bits) e i386 (32 bits), sempre que se usa ESP ou EBP numa referência à memória, automaticamente o seletor de segmento SS é usado. Por exemplo: mov eax,[esp] ; Lê dword de SS:ESP mov eax,[ebp] ; Lê dword de SS:EBP Assim, EBP pode ser usado para apontar para qualquer lugar da pilha. Em códigos não otimizados os compiladores de linguagens de alto nível usam EBP para marcar o ínicio do stack frame usado pela função. Desse jeito: f: push ebp ; salva EBP na pilha para recuperar depois. mov ebp,esp ; EBP agora têm o endereço desse novo "topo" da pilha. A partir daí você pode manipular ESP como bem entender (PUSHes, POPs) que EBP conterá o endereço da "base" do stack frame. No final da função basta recuperar o antigo valor de EBP e sair: pop ebp ; Recupera EBP da pilha. ret Mas, isso não significa que EBP seja "especializado" como registrador de "base" da pilha. Somente sua associação com SS é que o torna "especial". Mas, ele pode ser usado como um registrador de uso geral como os demais (EAX, EBX, ECX, EDX, ESI e EDI). Eu disse que esse "macete" é usado em códigos antigos e não otimizados porque é perfeitamente possível usar ESP para acessar memória em códigos de 32 e 64 bits (não é possível em 16 bits usando SP - a não ser usando ESP). E, de fato, o uso do prólogo (ajustar EBP) e o epílogo (recuperar o EBP) não é muito desejável por questões de performance e tamanho de código (compiladores como GCC, por exemplo, se usadas otimizações, implementam a opção -fomit-frame-pointer, que evitam a criação do prólogo e epílogo). Então, sim... no contexto de uma função, pode-se pensar no EBP como a "base" do stack frame. No contexto do processador, no entanto, ele é um registrador de uso geral como qualquer outro, exceto que referências à memória, usando-o, são associadas ao seletor SS, ao invés do seletor DS. Link para o comentário Compartilhar em outros sites More sharing options...
Sckoofer B Postado Janeiro 21, 2021 em 22:24 Compartilhar Postado Janeiro 21, 2021 em 22:24 Sim, muito interessante a explicação do Frederico, não sabia que sempre que se usa ESP ou EBP numa referência à memória, automaticamente um seletor seria usado. Na verdade eu achava que os seletores, como dito em duas video aulas de Assembly (e acho que no material de x64 que o Frederico escreveu) seriam usados em uma situação bem específica, por exemplo, programar um sistema operacional ou um driver, e que não deveríamos nos preocupar em usar os seletores. E outra coisa, não sabia que a ausencia do prólogo é sinônimo de código otimizado, já que os poucos códigos que ví , que foram gerados por um disassembler, todos eles usam o prólogo pra começar uma função. Também não sabia que o uso dele era opcional. Para divulgação de material daqueles que trazem conteúdo para o mente binária, o Fernando Fresteiro (M4st3r3k) também tem um vídeo mostrando o uso do Stack Frame e também explica sobre: Link para o comentário Compartilhar em outros sites More sharing options...
Jonathan403 Postado Janeiro 22, 2021 em 00:16 Autor Compartilhar Postado Janeiro 22, 2021 em 00:16 Entendido! Muito obrigado pela resposta Frederico e Sckoofer! Link para o comentário Compartilhar em outros sites More sharing options...
fredericopissarra Postado Janeiro 22, 2021 em 13:04 Compartilhar Postado Janeiro 22, 2021 em 13:04 14 horas atrás, Sckoofer B disse: Sim, muito interessante a explicação do Frederico, não sabia que sempre que se usa ESP ou EBP numa referência à memória, automaticamente um seletor seria usado. Na verdade eu achava que os seletores, como dito em duas video aulas de Assembly (e acho que no material de x64 que o Frederico escreveu) seriam usados em uma situação bem específica, por exemplo, programar um sistema operacional ou um driver, e que não deveríamos nos preocupar em usar os seletores. E outra coisa, não sabia que a ausencia do prólogo é sinônimo de código otimizado, já que os poucos códigos que ví , que foram gerados por um disassembler, todos eles usam o prólogo pra começar uma função. Também não sabia que o uso dele era opcional. Pois é... nos antigos 8086 até o 80286 só tínhamos os registradores AX, BX, CX, DX, SI, DI, BP, SP, além do IP e FLAGS e os seletores. Nos modos de endereçamento era possível usar apenas BX ou BP como endereço base e SI ou DI como índice e, ao usar BP o seletor SS era usado (ao usar BX, DS era usado). Nos 386 isso mudou e qualquer registrador de uso geral pode ser usado como base e índice, mas a seleção automática de SS quando usados ESP ou EBP continua valendo. Quanto ao "sinônimo" de código otimizado eis um exemplo simples: int fma_( int a, int b, int c ) { return a*b+c; } Se você compilar isso sem o uso de alguma opção de otimização: $ cc -S -o test1.s test.c Obterá algo assim: fma_: push rbp ; Prólogo mov rbp, rsp mov [rbp-4], edi ; Armazena os argumentos. mov [rbp-8], esi mov [rbp-12], edx ; Multiplica e soma, pegando os argumentos da pilha. mov eax, [rbp-4] imul eax, [rbp-8] mov edx, eax mov eax, [rbp-12] add eax, edx pop rbp ; Epílogo ret Note que, apesar os argumentos serem passados por registradores o compilador armazenou os argumentos na pilha (porque os argumentos são locais à função) e os leu, pela segunda vez, antes das operações... E os prólogo e epílogo estão lá... Basta adicionar -O2 na compilação para obter um código otimizado: fma_: imul edi, esi lea eax, [rdi+rdx] ret Sem armazenamento local e sem aquela preparação para o stack frame. No modo x86-64 a falta de otimização leva a aquela redundância de cópia/leitura... No modo i386 é um pouco melhor, uma vez que os argumentos já são passados pela pilha (exceto nos casos de convenções como fastcall, por exemplo): ; Sem -O2 fma_: push ebp mov ebp, esp mov eax, [ebp+8] imul eax, [ebp+12] mov edx, eax mov eax, [ebp+16] add eax, edx pop ebp ret ------------------------------ ; Com -O2 fma_: mov eax, [esp+8] imul eax, [esp+4] add eax, [esp+12] ret Note, no entanto que a função otimizada não usa EBP e acessa os argumentos diretamente da pilha. Link para o comentário Compartilhar em outros sites More sharing options...
fredericopissarra Postado Janeiro 22, 2021 em 13:23 Compartilhar Postado Janeiro 22, 2021 em 13:23 Um "macete" para evitar ficar lembrando os offsets na pilha é usar estruturas (exemplo com o NASM, código para i386): struc fmastkf .retaddr: resd 1 ; CALL colocar isso na pilha. .a: resd 1 .b: resd 1 .c: resd 1 endstruc fma_: mov eax,[esp+fmastkf.b] ; O compilador preferiu pegar b primeiro... imul eax,[esp+fmastkf.a] ; ... e multiplicar por a. add eax,[esp+fmastkf.c] ret O empilhamento, na chamada de fma_(), é claro, é feito de trás para frente. Assim, como a é empilhado por último, ele fica mais próximo de retaddr. Link para o comentário Compartilhar em outros sites More sharing options...
Posts Recomendados
Arquivado
Este tópico foi arquivado e está fechado para novas respostas.