Вызовы функций
Параметры обычно передаются в стеке, который является частью памяти, на которую указывает esp
. Операционная система отвечает за резервирование некоторой памяти для стека, а затем правильную настройку esp
перед передачей управления вашей программе.
Обычный вызов функции может выглядеть примерно так:
main:
push 456
push 123
call MyFunction
add esp, 8
ret
MyFunction:
; [esp+0] will hold the return address
; [esp+4] will hold the first parameter (123)
; [esp+8] will hold the second parameter (456)
;
; To return from here, we usually execute a 'ret' instruction,
; which is actually equivalent to:
;
; add esp, 4
; jmp [esp-4]
ret
Существуют разные обязанности, разделенные между вызывающей функцией и вызываемой функцией в отношении того, как они обещают сохранить регистры. Эти правила называются соглашениями о вызовах.
В приведенном выше примере используется соглашение о вызовах cdecl, что означает, что параметры помещаются в стек в обратном порядке, а вызывающая функция отвечает за восстановление esp
туда, где она указывала до того, как эти параметры были помещены в стек. куча. Это то, что делает add esp, 8
.
Основная функция
Обычно вы пишете функцию main
на ассемблере и собираете ее в объектный файл. Затем вы передаете этот объектный файл компоновщику для создания исполняемого файла.
Компоновщик отвечает за создание кода запуска, который правильно настраивает стек, прежде чем управление будет передано вашей main
функции, чтобы ваша функция могла действовать так, как если бы она вызывалась с двумя аргументами (argc/argv). То есть ваша функция main
не является реальной точкой входа, но код запуска переходит туда после установки аргументов argc/argv.
Код запуска
Так как же выглядит этот «код запуска»? Компоновщик создаст его для нас, но всегда интересно узнать, как это работает.
Это зависит от платформы, но я опишу типичный случай в Linux. Эта статья, хоть и устарела, объясняет расположение стека в Linux при запуске программы i386. Стек будет выглядеть так:
esp+00h: argc
esp+04h: argv[0]
esp+08h: argv[1]
esp+1Ch: argv[2]
...
Таким образом, код запуска может получить значения argc/argv из стека, а затем вызвать main(...)
с двумя параметрами:
; This is very incomplete startup code, but it illustrates the point
mov eax, [esp] ; eax = argc
lea edx, [esp+0x04] ; edx = argv
; push argv, and argc onto the stack (note the reverse order)
push edx
push eax
call main
;
; When main returns, use its return value (eax)
; to set an exit status
;
...
person
Martin
schedule
06.12.2011