Вызовы функций
Параметры обычно передаются в стек, который является частью памяти, на которую указывает 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
;
...