Строки представляют набор значений, который хранится в непрерывной области памяти. Процессор x86-64 поддерживает работу с 4 типами строк: строки значений byte, строки из чисел word, строки из двойных слов и строки из четверных слов. Для работы с этим строками процессор x86-64 предоставляет ряд инструкций для копирования, сравнения, поиска в строке и ряд других инструкций, которые далее будут рассмотрены.
Строковые инструкции используют определенные регистры в качестве операндов:
RSI
: хранит индекс значений исходной строки
RDI
: хранит индекс значений целевой строки
RCX
: счетчик значений
AL, AX, EAX
и RAX
Флаги в регистре FLAGS
Для копирования строк применяются следующие инструкции:
movsb
: извлекает байт по адресу RSI, сохраняет его по адресу RDI, а затем увеличивает или уменьшает регистры RSI и RDI на 1.
movsw
: выбирает слово по адресу RSI, сохраняет его по адресу RDI, а затем увеличивает или уменьшает RSI и RDI на 2.
movsd
: извлекает двойное слово по адресу RSI, сохраняет его по адресу RDI, а затем увеличивает или уменьшает адрес в регистрах RSI и RDI на 4.
movsq
: извлекает четверное слово по адресу RSI, сохраняет его по адресу RDI, а затем увеличивает или уменьшает адрес в регистрах RSI и RDI на 8.
Например, скопируем одну строку в другую:
.globl _start .data nums1: .word 10, 11, 12, 13, 14, 15, 16, 17 nums2: .fill 8, 2, 0 # 8 числа по 2 байта равных 0 .text _start: movq $nums1, %rsi # в RSI - откуда копируем movq $nums2, %rdi # в RDI - куда копируем movq $8, %rcx # в RCX - сколько копируем movsw # выполняем копирование # проверяем movq nums2, %rdi # RDI=10 movq $60, %rax syscall
Здесь копируем данные из nums1 в nums2. Для этого адрес исходной строки nums1 загружаем в RSI, а адрес целевой строки nums2 - в регистр RDI. В RCX помещаем количество элементов , которые
надо скопировать. Важно, что речь идет о количестве элементов, а не байт. Так, в данном случае мы хотим скопировать все 8 элементов из nums1 в nums2, которые в целом занимают 16 байт.
И для копирования слов выполняем инструкцию movsw
.
Однако по умолчанию все инструкции копирования помещают только первый элемент исходной строки в целевую строку. Но нам то нужно в данном случае скопировать 8 чисел. Для повторения копирования применяется префикс rep
Префикс rep (repeat повторять) указывается перед инструкцией, которая работает со строками. Так, если префикс rep
присутствует, при выполнении инструкции
movsb
процессор проверяет RCX, чтобы увидеть, содержит ли этот регистр число 0. Если не содержит,
то процессор перемещает байт из RSI в RDI и уменьшает значение в регистре RCX.
Этот процесс повторяется до тех пор, пока RCX не станет равным 0. Если RCX содержит 0 при первоначальном выполнении, инструкция movsb не будет копировать байты данных.
Например, изменим предыдущую программу, применив rep
:
.globl _start .data nums1: .word 10, 11, 12, 13, 14, 15, 16, 17 nums2: .fill 8, 2, 0 # 8 числа по 2 байта равных 0 .text _start: movq $nums1, %rsi movq $nums2, %rdi movq $8, %rcx rep movsw movq $60, %rax syscall
Префикс rep
указывает процессору повторить эту операцию столько раз, сколько указано в регистре RCX.
Стоит отметить, что конкретно в случае выше процесс копирования не является оптимальным: нам надо скопировать 8 слов или 16 байт, и это количество кратно 8. На первый взгляд для копирования
слов надо использовать инструкцию movsw
, которая собственно и предназначена для копирования слов. Но для ассемблера в данном случае нет разницы - скопирует он 8 раз по 2 байта последовательно
расположенные данные или скопирует те же данный 2 раза но по 8 байт. Поэтому в случае выше мы могли бы применить копирование по восьмеричным словам:
.globl _start .data nums1: .word 10, 11, 12, 13, 14, 15, 16, 17 nums2: .fill 8, 2, 0 # 8 числа по 2 байта равных 0 .text _start: movq $nums1, %rsi movq $nums2, %rdi movq $2, %rcx # 8 слов - это два четверных слова quad rep movsq # копируем по четверному слову quad movq $nums2, %rdi movq 4(%rdi), %rdi # проверяем 4-й элемент из nums2, он должен быть равен 12 movq $60, %rax syscall
Если количество байт не кратно 8 или вообще нечетное, то для большей эффективности можно комбинровать копирование различных данных:
.globl _start .data nums1: .word 10, 11, 12, 13, 14, 15, 16, 17, 18 nums2: .fill 9, 2, 0 .text _start: movq $nums1, %rsi movq $nums2, %rdi movq $2, %rcx # 9 слов - это 2 четверных слова + 1 слово rep movsq # копируем по четверному слову quad movsw # копируем одно слово movq $nums2, %rdi movq 16(%rdi), %rdi # проверяем 8-й элемент из nums2, он должен быть равен 18 movq $60, %rax syscall
В данном случае для копирования данных потребуется две инструкции movsq
и одна инструкция movsw
. Даная схема наиболее эффективна, если оба
массива выровнены по границам четырех слов. Если подобного выравнивания нет, то можно переместить инструкции, которые копируют меньшее количество данных - в данном случае movsw
до инструкции movsq
, чтобы movsq
работала с данными, выровненными по четырём словам:
.globl _start .data nums1: .word 10, 11, 12, 13, 14, 15, 16, 17, 18 nums2: .fill 9, 2, 0 .text _start: movq $nums1, %rsi movq $nums2, %rdi movq $2, %rcx # 9 слов - это 2 четверных слова + 1 слово movsw # копируем одно слово rep movsq # копируем по четверному слову quad movq $nums2, %rdi movq 16(%rdi), %rdi # проверяем 8-й элемент из nums2, он должен быть равен 18 movq $60, %rax syscall
Флаг направления в регистре FLAGS управляет навправлением копирования элементво строк. Если флаг направления сброшен, процессор увеличивает RSI в RDI после обработки каждого элемента строки.
Например, инструкции movs
скопируют байт, слово, двойное слово или четверное слово из RSI в RDI, а затем увеличат RSI и RDI на 1, 2, 4 или 8
(соответственно). Если флаг направления установлен, x86-64 уменьшает RSI в RDI после обработки каждого элемента строки.
Для управления флагами направления применяются инструкции cld (сброс флага направления) и std (установка флага направления). Например, скопируем данные из одной строки в другую в противоположном направлении:
.globl _start .data nums1: .word 10, 11, 12, 13, 14, 15, 16, 17 len = . - nums1 # размер массива count = len / elemSize # количество элементов elemSize = 2 # размер одного элемента lastPosition = len - elemSize # смещение последнего элемента относительно начала nums2: .fill 8, 2, 0 .text _start: movq $nums1, %rsi addq $lastPosition, %rsi # указывает на последний элемент movq $nums2, %rdi addq $lastPosition, %rdi # указывает на последний элемент movq $count, %rcx # RCX = 8 std # устанавливаем флаг направления rep movsw movq $nums2, %rdi movq 14(%rdi), %rdi # проверяем 7-й элемент из nums2, он должен быть равен 17 movq $60, %rax syscall
Здесь регистры RSI и RDI перед копированием строки указывают на последний элемент строки. И при вызове инструкции movsw
сначала копируются последниие элементы строки,
потом предпоследние и так далее вплоть до первого элемента.