Как машинный код получает доступ к параметрам вызова подпрограммы?

При запуске программы вы можете передавать параметры, например.

$ myProgram par1 par2 par3

В C вы можете получить доступ к этим параметрам, взглянув на argv,

int main (int argc, char *argv[]) 
{
     char* aParameter = argv[1];  // Not sure if this is 100% right but you get the idea...
}

Как это будет переведено в машинный код сборки/x86? Как бы вы получили доступ к данным вам переменным? Как система даст вам эти переменные?

Я очень новичок в сборке, похоже, вы можете получить доступ только к регистрам и абсолютным адресам. Я озадачен, как вы могли получить доступ к параметрам. Система предварительно загружает параметры в специальный регистр для вас?


person Robert    schedule 05.12.2011    source источник
comment
Как машинный код получает доступ к параметрам вызова подпрограммы?   -  person John Saunders    schedule 06.12.2011
comment
Хорошо, я изменил название, спасибо.   -  person Robert    schedule 06.12.2011


Ответы (3)


Вызовы функций

Параметры обычно передаются в стеке, который является частью памяти, на которую указывает 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
comment
Действительно отличный ответ. Одна вещь, которую вы упомянули о создании исполняемого файла из объектного кода... Является ли объектный код универсальным для любой платформы x86, но связывание его с исполняемым файлом делает его специфичным для определенной платформы, например. линукс? - person Robert; 06.12.2011
comment
@Robert: я оставлю этот вопрос для более квалифицированного ответа. Я полагаю, что объектный файл не является специфичным для ОС, но он может быть специфичным для создавшего его компилятора/компоновщика (на ум приходит изменение имени С++) - person Martin; 08.12.2011

Среда выполнения C выполняет здесь некоторую работу за вас — она извлекает аргументы программы из ОС и при необходимости анализирует их перед вызовом вашей функции main. В ассемблере вам придется извлекать аргументы команды и анализировать их самостоятельно. То, как вы получаете аргументы программы, зависит от ОС.

person mdma    schedule 05.12.2011
comment
Это ответственность среды выполнения? Я ничего не знаю в спецификации C, которая диктует, как должна вызываться точка входа в программу или как будут выполняться различные служебные обязанности (открытие стандартного ввода, вывода и т. д.) до вызова main. Конечно, это может быть реализовано таким образом, но есть и много других способов, не так ли? - person Ed S.; 06.12.2011
comment
main обычно похожа на любую другую функцию, вы увидите, что аргументы argc и argv поступают на основе соглашения о вызовах для этого компилятора и процессора. - person old_timer; 06.12.2011

Так же, как и ваша программа; вам просто нужно сделать это вручную.

Аргументы функций хранятся в различных регистрах/сегментах памяти до вызова функции. Когда вы вызываете функцию на ассемблере, вам нужно настроить стек вручную перед вызовом. Соглашение о вызовах определяет, куда идут эти переменные, как они упорядочиваются и как к ним осуществляется доступ.

Например, argc и argv будут созданы и помещены в стек. Данные, на которые они указывают, также уже были созданы. Когда функция вызывается, она знает, что аргументы 1..n будут помещены в какой-то раздел памяти в соответствии с соглашением о вызовах.

Вот краткий обзор соглашений о вызовах с несколькими примерами того, как стек быть настроены перед вызовом функции.

Кстати, перед вызовом main необходимо выполнить некоторый объем работы, и это скрыто от вас. Это хорошая вещь; мы не хотим писать кучу кода начальной загрузки каждый раз, когда начинаем новый проект.

person Ed S.    schedule 05.12.2011
comment
Это действительно отличный ответ, очень ясный и именно то, что я спрашивал. Спасибо! - person Robert; 06.12.2011