Сохранение регистров и переменных при вызове функций

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

Функция может активно задействовать регистры для различных задач. Например:

.globl _start 

.text
_start:
    movq $125, %rdi
    call sum
    addq %rax, %rdi    # RDI = 20

    movq $60, %rax
    syscall

sum:
    movq $5, %rdi
    movq $10, %rax
    addq %rdi, %rax
    ret

Для примера здесь определена функция sum, которая устанавливает регистры rax и rdi и складывает их значения, помещая результат в регистр rax.

В основной части программы помещаем в регистр RDI чило 125, затем вызываем функцию sum. Далее результат функции sum - содержимое регистра RDI складываем с числом из регистра RAX. Однако, поскольку вызов процедуры sum изменил значение регистра RDI, то в после ее вызова в этом регистре будет не число 125, а число 5. Соответственно итоговый результат может отличаться от ожидаемого. В данном случае при завершении программы в регистре RDI будет число 20, а не 140.

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

.globl _start 

.text
_start:
    movq $125, %rdi
    call sum
    addq %rax, %rdi    # RDI = 140

    movq $60, %rax
    syscall

sum:
    pushq %rdi       # сохраняем в стек регистр rdi
    movq $5, %rdi
    movq $10, %rax
    addq %rdi, %rax
    popq %rdi         # восстанавливаем из стека регистр rdi
    ret

В данном случае в начале функции sum сохраняем значение rdi, а при завершении восстанавливаем его. Подобным образом стоит сохранять и восстанавливать все используемые регистры в стек. Регистр RAX в данном случае не сохраняется, так как мы ожидаем, что через него основная часть программы получить результат процедуры sum.

При этом в принципе необязательно сохранять регистры в вызываемой функции, мы могли бы это сделать и в вызывающем коде:

.globl _start 

.text
_start:
    movq $125, %rdi
    pushq %rdi       # сохраняем в стек регистр rdi
    call sum
    popq %rdi         # восстанавливаем из стека регистр rdi
    addq %rax, %rdi    # RDI = 140

    movq $60, %rax
    syscall

sum:
    movq $5, %rdi
    movq $10, %rax
    addq %rdi, %rax
    ret

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

Сохранение и восстановление переменных

Все, что касается регистров, также можно отнести и к глобальным переменным, которые используются в процедурах:

.globl _start 

.data
    num: .quad 45
.text
_start:
    call sum
    movq %rax, %rdi     # RDI = RAX = 25
    addq num, %rdi     # RDI = RDI + num = 25 + 15 = 40

    movq $60, %rax
    syscall

sum:
    movq $15, num
    movq $10, %rax
    addq num, %rax     # RAX = RAX + num = 10 + 15 = 25
    ret

В данном случае в основной части программы мы ожидаем, что переменная num будет равна 45, но вызов функции sum меняет ее значение на 15. И подобным образом в вызываемой функции переменную можно сохранить в стек:

.globl _start 

.data
    num: .quad 45
.text
_start:
    call sum
    movq %rax, %rdi     # RDI = RAX = 25
    addq num, %rdi     # RDI = RDI + num = 25 + 45 = 70

    movq $60, %rax
    syscall

sum:
    pushq num        # сохраняем значение num в стек
    movq $15, num
    movq $10, %rax
    addq num, %rax     # RAX = RAX + num = 10 + 15 = 25
    popq num         # восстанавливаем num из стека
    ret

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