сборка x86: передать параметр функции через стек

Я пытаюсь сделать подпрограмму в сборке, которая нарисует квадрат на экране. Я не думаю, что могу передать параметры подпрограмме, как в C++, поэтому я решил, что могу использовать стек для хранения и доступа к параметрам (я не могу использовать общие регистры данных, потому что слишком много переменных для передачи).

проблема в том (я помню, что где-то читал), что когда я использую команду вызова по адресу текущей "программы", она сохраняется в стеке, поэтому что, когда он использует команду "ret", он будет знать, куда возвращаться. Но если я сохраню что-то в стеке, а затем вызову функцию, мне придется сохранить где-то адрес (то есть в верхней части стека), а затем безопасно всплывать параметры. Затем, после того, как код закончился и перед вызовом "ret", мне пришлось бы отодвинуть адрес.

Я прав? И, если да, где я могу сохранить адрес (я не думаю, что адрес имеет длину всего 1 байт, чтобы он вписывался в AX или BX или любой другой регистр данных). Могу ли я использовать IP для этого (хотя я знаю, что это используется для чего-то другого)?

вот что я себе представляю:

[BITS 16]
....
main:
  mov ax,100b
  push ax
  call rectangle ;??--pushes on the stack the current address?

jml $

rectangle:
  pop ax ;??--this is the addres of main right(where the call was made)?
  pop bx ;??--this is the real 100b, right?
  ....
  push ax
ret ;-uses the address saved in stack

2 ответов


обычно используется базовый указатель (bp на 16 бит, ebp на 32 бит), чтобы обратиться к параметрам и местным жителям.

основная идея заключается в том, что каждый раз, когда вы входите в функцию, вы сохраняете указатель стека внутри базового указателя, чтобы иметь указатель стека, когда функция вызывалась как "фиксированная опорная точка" во время выполнения функции. В этой схеме [ebp-something] обычно является локальным,[ebp+something] - Это параметр.

транспонирование типичного 32-разрядного, callee-cleanup соглашения о вызовах вы можете сделать так:

звонящий:

push param1
push param2
call subroutine

подпрограмма:

push bp       ; save old base pointer
mov bp,sp     ; use the current stack pointer as new base pointer
; now the situation of the stack is
; bp+0 => old base pointer
; bp+2 => return address
; bp+4 => param2
; bp+6 => param1
mov ax,[bp+4] ; that's param2
mov bx,[bp+6] ; that's param1
; ... do your stuff, use the stack all you want,
; just make sure that by when we get here push/pop have balanced out
pop bp        ; restore old base pointer
ret 4         ; return, popping the extra 4 bytes of the arguments in the process

это будет работать, за исключением того, что с точки зрения абонента, функции изменяет sp. В 32-битном большинстве соглашений о вызовах функции могут изменять только eax/ecx/edx, и должны сохранять / восстанавливать другие правила, если они хотят их использовать. Я предполагаю, что 16bit похож. (Хотя, конечно, в asm вы можете писать функции с любыми пользовательскими соглашениями о вызовах, которые вам нравятся.)

некоторые соглашения о вызовах ожидать, вызываемый поп аргументы толкнул вызывающим, так это вообще-то, работай в этом случае. The ret 4 в ответе Маттео делает это. (См. x86 tag wiki для информации о соглашениях о вызовах и тоннах других хороших ссылок.)


это супер-странно, и не лучший способ делать вещи, поэтому он обычно не используется. самая большая проблема заключается в том, что он дает вам доступ только к параметрам в порядке, а не случайный доступ. Вы можете получить доступ только к первым 6 или около того args, потому что у вас закончились регистры поп их.

он также связывает регистр, содержащий обратный адрес. x86 (до x86-64) имеет очень мало регистров, поэтому это действительно плохо. Вы можете нажать обратный адрес после того, как другие функции args в регистры, я думаю, чтобы освободить его для использования.

jmp ax технически будет работать вместо push/ret, но это побеждает предиктор обратного адреса, замедляя будущее ret инструкция.


но в любом случае, создание кадра стека с push bp / mov bp, sp универсально используется в 16-битном коде, потому что он дешевый и дает вам случайный доступ к стеку. ([sp +/- constant] не является допустимым режимом адресации в 16 бит (но это в 32 и 64bit). ([bp +/- constant] действителен). После этого вы можете re-load от их когда вам нужно.

в 32 и 64-битном коде компиляторы обычно используют режимы адресации, такие как [esp + 8] или что-то еще, вместо того, чтобы тратить инструкции и связывать ebp. (-fomit-frame-pointer по умолчанию). Это означает, что вы должны отслеживать изменения в esp для разработки правильного смещения для одних и тех же данных в разных инструкциях, поэтому он не популярен в рукописном asm, esp в учебниках / учебных материалах. В реальном коде вы, очевидно, делаете то, что наиболее эффективно, потому что если бы Вы были готовы пожертвовать эффективностью, вы бы просто использовали компилятор C.