Параметры функции

Последнее обновление: 26.09.2023

Параметры позволяют передавать в функцию из вне некоторые значения. Обычно для передачи параметров применяются регистры. Но поскольку количество регистров ограничено, и они могут использоваться для других целей, также можно передавать значения параметров через стек или через глобальные переменные. Можно комбинировать различные подходы.

Если параметров немного, распростренным способом является передача значений в функцию через регистры. Напимер, определим простейшую функцию, которая получает получает извне два числа и складывает их:

.globl _start 

.text
_start:
    movq $3, %rcx      # первый параметр для функции sum
    movq $4, %rdx      # второй параметр для функции sum
    call sum        

    movq $60, %rax
    syscall

sum:
    movq %rcx, %rdi    # в RDI копируем число из RCX
    addq %rdx, %rdi    # складываем с числом из RDX
    ret

Здесь мы предполагаем, что два числа в функцию sum будут передаваться через регистры RCX и RDX. В функции sum помещаем в RDI первое число из RCX и складываем его с числом из RDX. В итоге после выполнения этой программы в регистре RDI будет число 7.

Если мы сами пишем все функции программы на ассемблере, то мы можем установить себе свои собственные правила, каким образом, через какие именно регистры будут передаваться параметры в функцию. Однако если мы задействуем какие-то внешний функционал, например, взаимодействуем с функциями С/С++, то нам придется также применять те условности, которые накладывают эти функции и конкретные ОС. В частности, в при вызове функций C/C++ на Linux первые 6 параметров передаются последовательно через регистры RDI, RSI, RDX, RCX, R8 и R9.

Передача параметров через стек

Еще один способ передачи параметров представляет передача через стек:

.globl _start 

.text
_start:
    pushq $1  # 24(%rsp)
    pushq $2  # 16(%rsp)
    pushq $3  # 8(%rsp)

    call sum        

    addq $24, %rsp

    movq $60, %rax
    syscall

sum:
    movq 24(%rsp), %rdi   # RDI = 1
    addq 16(%rsp), %rdi   # RDI = RDI + 2 = 3
    addq 8(%rsp), %rdi    # RDI = RDI + 3 = 6
    ret

В основной части программы в функцию sum через стек передаются три параметра с помощью инструкции push

pushq $1  # 24(%rsp)
pushq $2  # 16(%rsp)
pushq $3  # 8(%rsp)

Поскольку инструкция push помещает в стек 8-байтные значения, то в данном случае три добавленных числа займут в стеке пространство в 24 байта.

После вызова функции sum это пространство очищается

addq $24, %rsp

В функции sum для получения параметров используем косвеную адресацию и смещение относительно указателя RSP. При вызовае функции sum стек условно будет выглядеть следующим образом:

RSP ----------------------0x00A0
адрес возврата
--------------------------0x00A8
        3
--------------------------0x00B0
        2
--------------------------0x00B8
        1       
--------------------------0x00C0

Регистр RSP будет указывать на ячейку с адресом возврата. Соответственно, чтобы получить тот или иной параметр, нам надо использовать смещение 8, 16 или 24:

movq 24(%rsp), %rdi   # RDI = 1

В итоге после выполнения программы в регистре %rdi будет число 6.

Однако добавление в стек с помощью инструкции pushq может быть не оптимальным способом. В частности, выделенная память для стека может быть избыточно. В нашем случае для наших трех чисел необязательно выделять 24 байта, можно обойтись гораздо меньшим объемом. В этом случае вызывающий код может вручную помещать значения в нужные области стека, используя регистр RSP и смещения:

.globl _start 

.text
_start:
    subq $3, %rsp  # резервируем для параметров 3 байта

    # помещаем в стек три числа по 1 байту
    movb $3, 2(%rsp)
    movb $4, 1(%rsp)
    movb $5, (%rsp)
    call sum

    addq $3, %rsp   # восстанавливаем указатель стека

    movq %rax, %rdi # для проверки состояния rax перемещаем значение в rdi

    movq $60, %rax
    syscall

sum:
    xorq %rax, %rax     # обнуляем регистр
    movb 10(%rsp), %al  # RAX = 2
    addb 9(%rsp), %al  # RAX = RAX + 4 = 6
    addb 8(%rsp), %al  # RAX = RAX + 6 = 12
    ret

Здесь также в стек помещаются три числа, но теперь они представлют 1-байтные числа. Соостветственно нам нужно всего лишь 3 байта:

movb $3, 2(%rsp)
movb $4, 1(%rsp)
movb $5, (%rsp)

При получении данных в функции sum при установке смещения по прежнему учитываем 8 байтов адреса возврата:

movb 10(%rsp), %al  # RAX = 2
addb 9(%rsp), %al  # RAX = RAX + 4 = 6
addb 8(%rsp), %al

Стоит отметить, что при выполнении других инструкций или при взаимодействии с функциями со сторонними API, например, с функциями C/C++ и API операционных систем есть свои ограничения на работу со стеком. Например, некоторые инструкции требуют, чтобы данные располагались в стеке по четным адресам, тогда естественно размещать в стеке по 1 байту будет проблематично. Кроме того, функции могут потребовать выравнивания стека по 16 байтам. Тогда придется выделять много пространства, большая часть которого может быть не заполнена и может расходоваться впустую..

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850