Первая стадия компиляции представляет предобработку кода препроцессором. На этой стадии препроцессор обработывает все директивы препроцессора в исходном коде и заменяет их на соответствующий им код. Рассмотрим некоторые наиболее распространенные директивы препроцессора.
Директиа %define позволяет определить константу:
global _start %define MAGIC_NUM 22 section .data num: db MAGIC_NUM ; num = 22 section .text _start: mov rdi, MAGIC_NUM ; в RDI число 22 mov rax, 60 syscall
В данном случае определена константа MAGIC_NUM. При обработке препроцессором все вхождения этой контанты в коде будут заменяться на ее значение.
Стоит отметить, что идентификаторы, которые определяются с помощью директив препроцессора, как MAGIC_NUM, еще называются препроцессорными символами (preprocessor symbol). Таким символы позволяют дать более описательные имена для данных и действий программы.
Директивы %define
могут определять более сложные выражения. Например:
global _start %define set_status mov rdi, %define STATUS_CODE rax section .text _start: mov STATUS_CODE, 45 set_status STATUS_CODE mov rax, 60 syscall
Здесь константа set_status определяет текст "mov rdi,", соответственно все входжения этой константы будут заменяться на данный текст. Второй препроцессорный символ - STATUS_CODE представляет регистр rax.
Одни константы могут зависеть от других:
%define i 1 %define d i * 3
Здесь константы i равна 1, а константа d зависит от i и равна i * 3.
NASM позволяет переопределить препроцессорный символ:
%define num 1 %define num 3
Однако %define
имеет проблему - она не позволяет вычислять арифметические выражения, в которых применяется та же константа. Например:
%define num 1 %define num num + 2 ; ! Ошибка
Для подобных случаев NASM предоставляет другую директиву - %assign, которая позволяет изменить значение символа:
%define num 1 %assign num num + 2
NASM позволяет организовать повторение некоторых действий с помощью директивы %rep:
%rep количество_повторений повторяемые действия %endrep
После %rep указывается количество повторений. Действия, которые надо повторять, помещаются между директивами %rep и %endrep. Пример применения:
global _start %define num 1 section .data ; цикл, 10 повторений %rep 10 %assign num num+1 %endrep status_code: dq num ; status_code = 11 section .text _start: mov rdi, [status_code] mov rax, 60 syscall
Здесь определяется символ num, который равен 1. В цикле последовательно прибавляем к num единицу. Количество повторений равно 10, поэтому финальное значение символа num равно 11
Циклы позволяют нам создать наборы данных или инструкций. Например, нам надо определить в программе набор факториалов:
global _start %define N 5 %define F 1 factorials: %assign i 1 %rep N %assign F F*i db F %assign i i+1 %endrep section .text _start: mov rdi, [factorials+4] ; пятый факториал mov rax, 60 syscall
Здесь в цикле изменяем значение символа F таким образом, чтобы он представлял произведение всех предыдущих чисел от 1 до i. В результате после прохода препроцессора мы получим следующий набор данных:
factorials: db 1 db 2 db 3 db 6 db 24 db 120