movb // копировать 1 байт
movw // копировать 2 байтa
movl // копировать 4 байта
movq // копировать 8 байт
leaq data, %rbx // загрузить адрес переменной data в регистр rbx
~={yellow}Способы адресации=~
// Способ 1: прямая адресация
movw 0x402008, %ax // скопировать 2 байта (адрес 0x402008) в регистр ax
// или по метке
movw M, %ax
// Способ 2: косвенная адресация
// Предварительно загружаем адрес в регистр (3 способа)
movq $0x402008, %rbx
movq $M, %rbx
leaq M, %rbx
// Скопировать в регистр %ax значение из памяти по тому адресу
// который содержиться в регистре %rbx
movw (%rbx), %ax
// Можно указать базовое смещение адреса (положительное - вправо, отрицательное - влево)
movw 8(%rbx), %ax
// Можно указать текущее смещение предварительно загрузив его в регистр
leaq data, %rbx
movq $8, %rcx
movw 0(%rbx, %rcx), %ax
// Можно указать базовое и текущее смещение одновременно
movw 0(%rbx, %rcx), %ax
// Можно указать размер позиции смещения.
// Вот здесь он равен 2-ум байтам
movw 4(%rbx, %rcx, 2), %ax
~={yellow}Адресация относительно регистра rip=~
%rip - Re-Extended Instruction Pointer - указатель команд. Хранит адрес очередной команды, которая будет выполнена.
Если в качестве базового смещения относительно регистра %rip указана метка, то на место этой метки будет поставлено расстояние в байтах от значения %rip до того адреса, который обозначен данной меткой
movb data(%rip), %al
Поместить значение регистра rip в другой регистр
leaq (%rip) %rax
PIC (PIE) - Position Independent Code Это код, который не зависит от абсолютных адресов. Все ссылки на ячейки памяти относительные (например относительно счетчика команд). Этот код может быть размещен в любой области памяти. Может использоваться при создании динамических библиотек.
~={yellow}Регистр указателя стека=~
%rsp - Re-extended Stack Pointer - Указатель стека В начале выполнения программы содержит адрес, слева от которого можно хранить данные.
Для работы со стеком используются два регистра: %rsp и %rdp
%rsp - адрес левой границы стека %rdp - адрес правой границы стека
Перед использованием стека нужно скопировать содержимое регистра %rsp в %rbp. Для того, чтобы выделить на стеке память под N байтов нужно вычесть число N из регистра %rsp, тем самым "сместить" %rsp влево на N байтов.
// Можно загрузить двухбайтовое значение в стек
movw $0xabcd, 0(%rsp)
// Или 4-ех байтовое
movl 0x12345678, 2(%rsp)
// Но 8-и байтовое загрузить напрямую нельзя
subq %8, %rsp; // выделить 8 байт
movq $0x0123456789abxdef, %rax // запись в регистр
movq %rax, 0(%rsp) // перемещение в стек
// Загрузка данных в стек частями
subq %1, %rsp; // выделить 1 байт в стеке
movb $0x12, 0(%rsp); // поместить 1 байт в стек
// Далее помещаем два байта в стек
subq %2, %rsp; // выделить 1 байт в стеке
movb $0x1234, 0(%rsp); // поместить 1 байт в стек
// Moжно загрузить в стек 2 (8) байта одной командой
pushw %0x1234
movq $0x0123456789abxdef, %rax // запись в регистр
pushq %rax;
// Очисить стек
movq %rbp, %rsp
Если в программе применяются функции, использующие стандартизованные соглашения о вызовах функций (например функции стандартной библиотеки языка Си), то стек должен быть выровнен по 16-байтовой границе, т.е. адреса правой и левой границ стека должны быть кратны 16 (Шестнадцатеричное представление должно оканчиваться на 0).
В начале программы адрес правой границы стека изначально кратен 16. Далее, память на стеке нужно выделять порциями, объем которых кратен 16.
~={yellow}Вывод строки на экран=~
Для вывода строки на экран можно воспользоваться системной функцией sys_write. Для этого номер этой функции нужно загрузить в регистр %rax.
У функции sys_write три параметра:
fd - file descriptor, загружается в регистр %rdi = целое положительное число, которое определяет, куда будет производиться вывод. Для вывода на экран нужно строку отправить в выходной поток. Дескриптор выходного потока равен 1.
2-ой параметр buf - buffer - загружается в регистр %rsi - адрес первого байта выводимых данных (адрес начала строки, которую нужно вывести).
3-ий параметр count - загружается в регистр %rdx - количество выводимых байтов (символов)
// Директивы .string и .asciz добавляют в конце строки нулевой байт.
Можно вставлять символы по кодам
.ascii "test\12"; // это код символа в восьмеричной системе
.ascii "test\x0a"; // это в шестнадцатиричной
Так же работает обычное экранирование символов
~={yellow} Бинарные и текстовые файлы=~ Текстовый файл - файл в котором каждый байт соответствует коду печатного символа (а так же управляющие элементы: перенос строки, табуляция и т.д.).
~={yellow}Сегменты программы=~
Сегмент кода является главным в программе, именно он определяет последовательность действий, которые выполняет программа. Сегмент кода является обязательным, в то время как других сегментов может не быть, так как сами данные без кода - это уже не программа. Объявляется директивой .text.
Сегмент данных начинается с директивы .data.
~={yellow}JMP - команда безусловного перехода=~
// Переход по на метку
jmp M
// или
addr:
.quad 0x401023
_start:
jmp *addr
// или загрузить адрес метки в регистр
_start
movq $M, %rax
jmp *%rax
// или поместить нужный адрес в сегмент стека
_start:
movq %rsp, %rdp
movq $M, -8(%rdp)
jmpq *-8(%rdp); // jmpq - если адрес перехода где-то хранится
~={yellow}Команды call и ret (продвинутый jmp)=~
Команда ~={magenta}call=~ осуществляет переход по указанному адресу, предварительно помещая в стек адрес команды, которая идет после команды call.
Команда ~={magenta}ret=~ извлекает 8-байтовый адрес из стека (при этом освобождая 8 байт на стеке) и осуществляет переход по этому адресу.
Команды ~={magenta}call и ret=~ используются для реализации механизма подпрограмм и функций. Команда call (вызвать) используется для для перехода из основной программы в подпрограмму, при этом сохраняя на стеке так называемый адрес возврата. Команда ret (вернуться) помещается в конец подпрограммы и осуществляет возврат в основную программу по этому адресу, который был ранее занесен в стек командой call.
Порядок аргументов при вызове функции с помощью call в Linux x86-64 (ABI System V):
1-ый параметр - %rdi 2-ый параметр - %rsi 3-ый параметр - %rdx 4-ый параметр - %rcx 5-ый параметр - %r8 6-ый параметр - %r9
В регистр %rax помещается количество используемых xmm-регистров.
~={yellow}Функция Write из динамической библиотеки языка C=~ ~={yellow}Параметры:=~ 1-ый параметр fd - file descriptor - целое положительное число, которое определят, куда будет производиться вывод. Для вывода на экран нужно строку отправить в выходной поток. Дескриптор выходного потока равен 1.
2-ой параметр buf - buffer, адрес первого байта выводимых данных (адрес начала строки, которую нужно вывести).
3-ий параметр count - количество выводимых байтов (символов)
Остальные аргументы через стек
~={yellow}Таблица символов=~
Метка - это символьный псевдоним адреса того что идет за меткой (адрес команды, адрес данных). Метка - это еще символьное имя или символ. Символы хранятся в исполняемом файлу в таблице символов. Вывести ее содержимое можно следующим образом:
$ readelf -s main.exe
Можно указать тип метки
.type str, @object // эта метка ссылается на область хранения данных
.type _start, @function // эта метка ссылается на адрес хранения команды
~={yellow}Директивы объявления символов=~
# Адрес начала строки указан с использованием абсолютной адресации
str:
.ascii "test\n"
leaq str %rsi
# Если мы знаем адрес начала строки, то можно создать символ так
.equ str, 0x402000
# Можно сделать и так
.set str, 0x402000
# Или так. Это директива, а не команда.
str = 0x402000
# Запрещает повторное определение символа
.equiv str, 0x402000
~={yellow}Директива .rodata=~
Данные под этой директивой не могут изменяться в процессе выполнения программы. Они только для чтения.
.section .rodata
~={green}остановился на начале 24=~