Как меняется указатель стека в этой программе с call и ret

Мои вопросы относятся к действиям, которые происходят между строками при изменении контекста, особенно в отношении RSP и RBP.

Учитывая эту очень простую программу:

Reading symbols from ./function_call...done.
(gdb) disass main
Dump of assembler code for function main:
   0x00000000004004d6 <+0>: push   rbp
   0x00000000004004d7 <+1>: mov    rbp,rsp
   0x00000000004004da <+4>: mov    esi,0x2
   0x00000000004004df <+9>: mov    edi,0x1
   0x00000000004004e4 <+14>:    call   0x4004b6 <add_and_7>
   0x00000000004004e9 <+19>:    mov    eax,0x0
   0x00000000004004ee <+24>:    pop    rbp
   0x00000000004004ef <+25>:    ret    
End of assembler dump.
(gdb) disass add_and_7
Dump of assembler code for function add_and_7:
   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x14],edi
   0x00000000004004bd <+7>: mov    DWORD PTR [rbp-0x18],esi
   0x00000000004004c0 <+10>:    mov    DWORD PTR [rbp-0x4],0x7
   0x00000000004004c7 <+17>:    mov    edx,DWORD PTR [rbp-0x14]
   0x00000000004004ca <+20>:    mov    eax,DWORD PTR [rbp-0x18]
   0x00000000004004cd <+23>:    add    edx,eax
   0x00000000004004cf <+25>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004d2 <+28>:    add    eax,edx
   0x00000000004004d4 <+30>:    pop    rbp
   0x00000000004004d5 <+31>:    ret    
End of assembler dump.
(gdb) list
1   int add_and_7( int num1, int num2 ) {
2       int seven = 7;
3       return num1 + num2 + seven;
4   }
5   
6   int main() {
7       add_and_7( 1, 2 );
8       return 0;
9   }

Все функции начинаются с push rbp, который, насколько я понимаю, сохраняет родительский контекст в стеке. Откуда родительская функция знает, как перестроить себя? Встроены ли необходимые шаги в call и ret?

Затем rsp всегда перемещается на rbp. Как я читал, это устанавливает новую базу стека в контексте текущей функции. Что я не могу понять, так это когда и как указатель стека был установлен в эту точку в первую очередь. Насколько я понимаю, это делает вызов функции ассемблера, это то, что происходит?

Наконец, когда метод возвращает значение, кажется, что eax — это регистр, который используется родительской функцией для использования возврата ее дочерней функции. Используется ли eax для этого явно или это просто соглашение с моим компилятором и архитектурой?


person Tyrel Richey    schedule 24.08.2014    source источник
comment
Google для соглашения о вызовах. Программист или компилятор должен знать, что будет изменено функцией, и соответствующим образом запрограммировать свою программу.   -  person rkhb    schedule 24.08.2014
comment
Спасибо, en.wikipedia.org/wiki/X86_calling_conventions очень помогло! Просто нужен был правильный поисковый запрос больше всего на свете. Хотя, если кто-то хочет уточнить, чем больше знаний, тем лучше.   -  person Tyrel Richey    schedule 24.08.2014


Ответы (2)


Откуда родительская функция знает, как перестроить себя? Встроены ли необходимые шаги в call и ret?

Перед вызовом функции сохраняется текущее состояние регистров, а также адрес возврата. Инструкция call переходит к определенному адресу, где начинается вызываемая функция. Адрес возврата помещается в стек. Когда вызванная функция возвращается, инструкция ret извлекает ранее отправленный адрес возврата и переходит к этому местоположению.

Тогда rsp всегда перемещается в rbp

rbp предварительно помещается в стек, чтобы иметь возможность восстановить значение rbp из вызывающей функции. Затем rsp перемещается в rbp, чтобы создать новый кадр стека для вызываемой функции. Новый базовый указатель настроен. Итак, в настоящее время rbp и rsp указывают на одни и те же адреса. Если есть другие push инструкции, esp автоматически настраивается. Когда функция выполнена, инструкция pop ebp восстанавливает ранее отправленный адрес базового указателя стека.

person macfij    schedule 24.08.2014
comment
Я думаю, у вас опечатка. RBP никогда не перемещается в RSP. - person Bartlomiej Lewandowski; 25.08.2014

Push и Pop модифицируют указатель стека — SP.

Вызов выдвигает ФЛАГИ - регистр состояния, а также РА - обратный адрес. Ret выталкивает FLAGS и переходит к обратному адресу.

Как сказал rkhb, необходимость сохранять определенные регистры в том виде, в каком они есть, исходит из соглашений о вызовах.

person Bartlomiej Lewandowski    schedule 24.08.2014
comment
call/ret не трогайте ФЛАГИ. Может быть, вы думаете о int 0x80 / iret? - person Peter Cordes; 26.05.2018