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

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

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

global _start

section .text
_start:
    mov rdi, 125
    mov rax, 15
    call sum
    add rdi, rax    ; RDI = 25
    mov rax, 60
    syscall 

; определяем функцию sum
sum:
    mov rdi, 5
    mov rax, 10
    add rax, rdi
    ret

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

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

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

global _start

section .text
_start:
    mov rdi, 125
    call sum
    add rdi, rax    ; RDI = 140
    mov rax, 60
    syscall 

; определяем функцию sum
sum:
    push rdi       ; сохраняем в стек регистр rdi
    mov rdi, 5
    mov rax, 10
    add rax, rdi
    pop rdi         ; восстанавливаем из стека регистр rdi
    ret

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

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

global _start

section .text
_start:
    mov rdi, 125
    push rdi       ; сохраняем в стек регистр rdi
    call sum
    pop rdi         ; восстанавливаем из стека регистр rdi
    add rdi, rax    ; RDI = 140
    mov rax, 60
    syscall 

; определяем функцию sum
sum:
    mov rdi, 5
    mov rax, 10
    add rax, rdi
    ret

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

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

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

global _start

section .data
num dq 45

section .text
_start:
    call sum
    mov rdi, rax     ; RDI = RAX = 25
    add rdi, [num]   ; RDI = RDI + num = 25 + 15 = 40
    mov rax, 60
    syscall 

; определяем функцию sum
sum:
    mov qword [num], 15    ; изменяем переменную num
    mov rax, 10
    add rax, [num]     ; RAX = RAX + num = 10 + 15 = 25
    ret

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

global _start

section .data
num dq 45

section .text
_start:
    call sum
    mov rdi, rax     ; RDI = RAX = 25
    add rdi, [num]   ; RDI = RDI + num = 25 + 45 = 70
    mov rax, 60
    syscall 

; определяем функцию sum
sum:
    push qword [num]        ; сохраняем значение num в стек
    mov qword [num], 15    ; изменяем переменную num
    mov rax, 10
    add rax, [num]     ; RAX = RAX + num = 10 + 15 = 25
    pop qword [num]         ; восстанавливаем num из стека
    ret

Аналогичный пример на Windows:

global _start

section .data
num dq 45

section .text
_start:
    call sum
    add rax, [rel num]   ; RDI = RDI + num = 25 + 45 = 70
    ret

; определяем функцию sum
sum:
    push qword [rel num]        ; сохраняем значение num в стек
    mov qword [rel num], 15    ; изменяем переменную num
    mov rax, 10
    add rax, [rel num]     ; RAX = RAX + num = 10 + 15 = 25
    pop qword [rel num]         ; восстанавливаем num из стека
    ret
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850