Как машинный код обращается к параметрам для вызова подпрограммы? - PullRequest
5 голосов
/ 06 декабря 2011

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

$ 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?Как бы вы получили доступ к данным переменным?Как система выдаст вам эти переменные?

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

Ответы [ 3 ]

9 голосов
/ 06 декабря 2011

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

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

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

4 голосов
/ 06 декабря 2011

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

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

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

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

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

...