Локальные метки

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

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

global _start

section .data
nums dq 5, 6, 7, 8, 1, 2, 3, 4
numSize equ 8   ; размер одного числа
count equ ($-nums)/numSize   ; количество чисел

section .text
_start:
    mov rdi, nums   ; в RDI - адрес массива
    mov rsi, count  ; в RSI - количество элементов
    call max

    mov rdi, rax    ; в RDI из RAX количество выведенных на консоль байтов 
    mov rax, 60
    syscall 

; Функция min возвращает минимальное значение в массиве
; RDI - ссылка на массив
; RSI - количество элементов в массиве
; RAX - результат функции - наименьшее значение. При 0 элементов результат неопределен
; выполняем (RSI-1) сравнений RAX с каждым элементом кроме первого
min:
    mov rax, [rdi]     ; RAX по умолчанию указывает на первый элемент
    jmp condition ; переходим к проверке условия
while:
    dec rsi     ; уменьшаем счетчик количества элементов массива
    cmp rax, [rdi + rsi* numSize]    ;  сравниваем rax с элементами, начиная с последних
    jb condition   ; если RAX меньше, переходим к проверке условия
    mov rax, [rdi + rsi*numSize]    ; если RAX больше, то помещаем меньшее число в RAX
condition:
    cmp rsi, 1   ; если элементов больше 1
    ja while   ; то выполняем цикл
    ret

; Функция max возвращает максимальное значение в массиве
; RDI - ссылка на массив
; RSI - количество элементов в массиве
; RAX - результат функции - наибольшее значение. При 0 элементов результат неопределен
; выполняем (RSI-1) сравнений RAX с каждым элементом кроме первого
max:
    mov rax, [rdi]     ; RAX по умолчанию указывает на первый элемент
    jmp condition ; переходим к проверке условия
while:
    dec rsi     ; уменьшаем счетчик количества элементов массива
    cmp rax, [rdi + rsi* numSize]    ;  сравниваем rax с элементами, начиная с последних
    ja condition   ; если RAX больше, переходим к проверке условия
    mov rax, [rdi + rsi*numSize]    ; если RAX меньше, то помещаем большее число в RAX
condition:
    cmp rsi, 1   ; если элементов больше 1
    ja while   ; то выполняем цикл
    ret

Здесь определены однотипные функции min и max, которые вычисляют соответственно минимальное и максимальное значенние из некоторого массива. Внутри каждой из функций применяются две метки - while и condition, которые управляют циклом. В цикле мы проходим последовательно по всем элементам массива, начиная с последнего, и сравниваем их со значением в регистре RAX. Однако проблема этой программы в том, что названия меток дублируются. Поэтому при компиляции мы получим ошибку:

root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o
hello.asm:44: error: label `while' inconsistently redefined
hello.asm:26: info: label `while' originally defined here
hello.asm:49: error: label `condition' inconsistently redefined
hello.asm:31: info: label `condition' originally defined here

Мы можем выйти из ситуации, изменив названия меток, чтобы они отличались. Однако, возможно, мы захотим оставить названия меток, учитывая, что здесь однотипные функции. А если программа большая, управлять метками становится гораздо сложнее. И NASM предоставляет решение - локальные метки. Локальные метки по сути те же метки, только перед их названием ставится точка, например:

.while: 
.condition:

Локальные метки доступны только в рамках кода глобальной метки, которая определена перед локальными метками. Например:

func1:
.label1:

func2:
.label2:

Здесь метка ".label1" определена в рамках кода метки "func1" (метку "func1" можно назвать "глобальной"), поэтому ".label1" доступна в рамках кода метки "func1", но недоступна в рамках кода метки "func2".

Так, изменим предыдущую программу, применив локальные метки:

global _start

section .data
nums dq 5, 6, 7, 8, 1, 2, 3, 4
numSize equ 8   ; размер одного числа
count equ ($-nums)/numSize   ; количество чисел

section .text
_start:
    mov rdi, nums          ; в RDI - адрес массива
    mov rsi, count     ; в RSI - количество элементов
    call max

    mov rdi, rax         ; в RDI из RAX количество выведенных на консоль байтов 
    mov rax, 60
    syscall 

; Функция min возвращает минимальное значение в массиве
; RDI - ссылка на массив
; RSI - количество элементов в массиве
; RAX - результат функции - наименьшее значение. При 0 элементов результат неопределен
; выполняем (RSI-1) сравнений RAX с каждым элементом кроме первого
min:
    mov rax, [rdi]     ; RAX по умолчанию указывает на первый элемент
    jmp .condition ; переходим к проверке условия
.while:
    dec rsi     ; уменьшаем счетчик количества элементов массива
    cmp rax, [rdi + rsi* numSize]    ;  сравниваем rax с элементами, начиная с последних
    jb .condition   ; если RAX меньше, переходим к проверке условия
    mov rax, [rdi + rsi*numSize]    ; если RAX больше, то помещаем меньшее число в RAX
.condition:
    cmp rsi, 1   ; если элементов больше 1
    ja .while   ; то выполняем цикл
    ret

; Функция max возвращает максимальное значение в массиве
; RDI - ссылка на массив
; RSI - количество элементов в массиве
; RAX - результат функции - наибольшее значение. При 0 элементов результат неопределен
; выполняем (RSI-1) сравнений RAX с каждым элементом кроме первого
max:
    mov rax, [rdi]     ; RAX по умолчанию указывает на первый элемент
    jmp .condition ; переходим к проверке условия
.while:
    dec rsi     ; уменьшаем счетчик количества элементов массива
    cmp rax, [rdi + rsi* numSize]    ;  сравниваем rax с элементами, начиная с последних
    ja .condition   ; если RAX больше, переходим к проверке условия
    mov rax, [rdi + rsi*numSize]    ; если RAX меньше, то помещаем большее число в RAX
.condition:
    cmp rsi, 1   ; если элементов больше 1
    ja .while   ; то выполняем цикл
    ret

Теперь метки .while и .condition являются локальными по отношению к меткам-функциям, в которых они объявлены. И проблем с компиляцией такой программы не возникнет.

Но стоит сказать, как NASM обарабатывает подобные метки. В реальности для NASM локальная метка идентична:

имя_глобальной_метки.имя_локальной_метки

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

max:
    mov rax, [rdi]     
    jmp max.condition ; переходим к локальной метке по ее полному имени
.while:
    dec rsi   
    cmp rax, [rdi + rsi* numSize]  
    ja max.condition   ; переходим к локальной метке по ее полному имени
    mov rax, [rdi + rsi*numSize] 
.condition:
    cmp rsi, 1 
    ja max.while   ; переходим к локальной метке по ее полному имени
    ret
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850