Что происходит на языке ассемблера при вызове метода/функции?
Если у меня есть программа в С++/C, то (язык не имеет большого значения, просто необходимо проиллюстрировать концепцию):
#include <iostream>
void foo() {
printf("in foo");
}
int main() {
foo();
return 0;
}
Что происходит в сборке? Я на самом деле не ищу ассемблерный код, так как я еще не получил его еще, но какой основной принцип?
Ответы
Ответ 1
В общем, это то, что происходит:
- Аргументы функции хранятся в стеке. В специфическом для платформы порядке.
- Местоположение для возвращаемого значения "выделено" в стеке
- Обратный адрес для функции также сохраняется в стеке или в регистре CPU специального назначения.
- Функция (или, фактически, адрес функции) вызывается либо с помощью специальной команды
call
, либо с помощью обычной команды jmp
или br
(переход/ветвь)
- Функция считывает аргументы (если есть) из стека и запускает код функции
- Возвращаемое значение из функции хранится в указанном месте (стек или регистр CPU специального назначения)
- Выполнение возвращается к вызывающему, и стек очищается (путем восстановления указателя стека до его начального значения).
Подробности выше варьируются от платформы к платформе и даже от компилятора к компилятору (см., например, соглашения STDCALL и CDECL). Например, в некоторых случаях регистры CPU используются вместо хранения материала в стеке. Общая идея такая же, хотя
Ответ 2
Вы можете увидеть это сами:
Под Linux "скомпилируйте" свою программу с помощью:
gcc -S myprogram.c
И вы получите список программ в ассемблере (myprogram.s).
Конечно, вы должны знать немного об ассемблере, чтобы понять это (но это стоит изучить, потому что это помогает понять, как работает ваш компьютер). Вызов функции (в архитектуре x86) в основном:
- поместить переменную a в стек
- положить переменную b в стек
- поместить переменную n в стек
- перейти к адресу функции
- загрузить переменные из стека
- делать вещи в функции
- чистый стек
- вернуться к главному
Ответ 3
Что происходит в сборке?
Краткое пояснение: текущее состояние стека сохраняется, создается новый стек и загружается и запускается код исполняемой функции. Это включает в себя неудобство нескольких регистров вашего микропроцессора, некоторые безумные, чтобы читать и записывать в память, и как только это делается, состояние стека вызывающей функции восстанавливается.
Ответ 4
Аргументы вставляются в стек и выполняется команда "вызов"
Вызов - это простой "jmp" с нажатием адреса инструкции в стек ( "ret" в конце метода, который выталкивает его и прыгает на него)
Ответ 5
Я думаю, вы хотите взглянуть на стек вызовов, чтобы лучше понять, что происходит во время вызова функции: http://en.wikipedia.org/wiki/Call_stack
Ответ 6
Очень хорошая иллюстрация:
http://www.cs.uleth.ca/~holzmann/C/system/memorylayout.pdf
Ответ 7
Что происходит?
C имитирует, что произойдет в сборке...
Это так близко к машине, что вы можете понять, что произойдет
void foo() {
printf("in foo");
/*
db mystring 'in foo'
mov eax, dword ptr mystring
mov edx , dword ptr _printf
push eax
call edx
add esp, 8
ret
//thats it
*/
}
int main() {
foo();
return 0;
}
Ответ 8
Общая идея заключается в том, что регистры, которые используются в вызывающем методе, помещаются в стек (указатель стека находится в регистре ESP
), этот процесс называется "нажимать регистры". Иногда они также обнуляются, но это зависит. Сборочные программисты стремятся освободить больше регистров, чем обычные 4 (EAX
, EBX
, ECX
и EDX
на x86), чтобы иметь больше возможностей внутри функции.
Когда функция заканчивается, то же происходит в обратном порядке: стек восстанавливается до состояния перед вызовом. Это называется "всплыванием регистров".
Обновление: этот процесс не обязательно должен произойти. Компиляторы могут оптимизировать его и встроить ваши функции.
Обновление: обычно параметры функции помещаются в стек в обратном порядке, когда они извлекаются из стека, они выглядят как в обычном порядке. Этот порядок не гарантируется C. (ref: Inner Loops
Риком Бутом)
Ответ 9
1- вызывающий контекст устанавливается в стеке
2- параметры помещаются в стек
3 "вызов" выполняется для метода
Ответ 10
Что происходит? В x86 первая строка вашей основной функции может выглядеть примерно так:
call foo
Команда call
будет выталкивать адрес возврата в стек, а затем jmp
в местоположение foo.
Ответ 11
Общая идея заключается в том, что вам нужно
- Сохранить текущее локальное состояние
- Передайте аргументы функции
- Вызов фактической функции. Это связано с тем, что где-то помещается адрес возврата, поэтому инструкция
RET
знает, где продолжить.
Специфика варьируется от архитектуры к архитектуре. И еще более специфические особенности могут варьироваться в зависимости от разных языков. Хотя обычно есть способы контролировать это до некоторой степени, чтобы обеспечить интероперабельность между разными языками.
Довольно полезной отправной точкой является статья Википедии о соглашениях о вызовах. Например, на x86 стек почти всегда используется для передачи аргументов в функции. Однако во многих архитектурах RISC регистры используются в основном, тогда как стек необходим только в исключительных случаях.