Процедуры

Определение и вызов процедур

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

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

Для определения процедуры применяется выражение

proc_name proc

где proc_name - имя процедуры.

Завершается определение процедуры выражением

proc_name endp

где proc_name - также имя процедуры.

Между proc_name proc и proc_name endp идет произвольный набор инструкций. Перед завершением процедуры помещается инструкция ret, которая передает управление в вызывающий код. Например:

.code
setReg proc     ; начало процедуры setReg
    mov rax, 10
    ret
setReg endp     ; конец процедуры setReg

main proc
    ret
main endp
end

Здесь определены две процедуры - главная функция main и процедура setReg. В процедуре setReg устанавливается значение регистра rax. При компиляции подобной программы (допустим, она находится в файле hello.asm) с помощью команды

ml64 hello.asm /link /entry:main

благодаря флагу /entry:main будет создавать файл, при запуске которого выполняется процедура с именем main. Но в нашем случае эта процедура пока ничего не делает, а процедура setReg автоматически не выполняется. Чтобы выполнить одну процедуру/функцию в другой, необходимо использовать инструкцию call, после которой указывается имя выполняемой процедуры:

call proc_name

Инструкция call помещает в стек 64-битный адрес инструкции, которая идет сразу после вызова. Значение, которое вызов помещает в стек, называется адресом возврата. Когда процедура завершает выполнение, для возвращения к вызывающему коду она выполняет инструкцию ret. Команда ret извлекает 64-битный адрес возврата из стека и косвенно передает управление на этот адрес.

Например, выполним процедуру setReg в функции main:

.code
setReg proc     ; начало процедуры setReg
    mov rax, 10
    ret
setReg endp     ; конец процедуры setReg

main proc
    call setReg ; вызов процедуры setReg
    ret
main endp
end

Вызываемые процедуры могут, в свою оцередь вызывать другие процедуры. Например:

.code
inner proc
    add rax, 1
    ret
inner endp

outer proc
    call inner
    add rax, 1
    ret
outer endp

main proc
    mov rax, 1
    call outer
    ret
main endp
end

Здесь функция main вызывает процедуру outer, а та вызывает процедуру inner. В итоге к завершению программы в регистре RAX будет число 3.

Стек и процедуры

При работе со стеком в процедурах следует учитывать, что вызов процедуры с помощью инструкции call помещает в стек адрес возврата. При завершении процедуры инструкция ret извлечет этот адрес возврата из стека и перейдет по этому адресу. Таким образом, выполнение вернется в код, где была вызвана процедура. Поэтому при вызове инструкции ret (при завершении процедуры) адрес возврата должен быть в верхушке стека.

Но при невнимательности это требование может быть нарушено. Например:

.code
sum proc
    push rbx        ; сохраняем регистр RBX в стек
    add rax, rbx
    ret             ; регистр RBX НЕ восстанавливаем
sum endp

main proc
    mov rax, 1
    mov rbx, 2
    call sum
    ret
main endp
end

Здесь вызывается процедура sum, в которой в стек сохраняется регистр RBX. Однако в конце процедуры регистр RBX не восстанавливается. Поэтому в качестве адреса вохврата будет рассматриваться значение регистра RBX, которое при вызовае инструкции ret будет находится в верхушке стека. В итоге поведение программы неопределено, и скорее всего она завершится ошибкой.

Другой пример - извлечение адреса возврата до завершения процедуры:

.code
sum proc
    pop rbx        ; извлекаем данные из стека в регистр RBX
    add rax, rbx
    ret             ; адрес возврата неопределен
sum endp

main proc
    mov rax, 1
    mov rbx, 2
    call sum
    ret
main endp
end

Здесь в регистр RBX извлекаются данные из стека - по сути в него извлекается адрес возврата. В результате опять же поведение программы неопределено.

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

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