сборка 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.