Assembly
Вступ до асемблера

Вступ до асемблера

Інструкції

Тепер, коли ви розумієте регістри та області пам'яті sBPF, давайте розглянемо інструкції, які ними маніпулюють.

Інструкції — це фундаментальні операції, які виконує ваша програма: додавання чисел, завантаження з пам'яті або перехід до різних місць.

Що таке інструкції?

Інструкції — це основні будівельні блоки вашої програми. Уявіть їх як команди, які точно вказують процесору, що робити:

  • add64 r1, r2: "Додати значення в регістрах r1 та r2, зберегти результат в r1"
  • ldxdw r0, [r10 - 8]: "Завантажити 8 байтів зі стекової пам'яті в регістр r0"
  • jeq r1, 42, +3: "Якщо r1 дорівнює 42, перейти вперед на 3 інструкції"

Кожна інструкція виконує рівно одну операцію і кодується як рівно 8 байтів даних для миттєвого декодування віртуальною машиною.

Інструкції sBPF працюють з різними розмірами даних:

  • байт = 8 бітів (1 байт)
  • півслово = 16 бітів (2 байти)
  • слово = 32 біти (4 байти)
  • подвійне слово = 64 біти (8 байтів)

Більшість операцій sBPF використовують 64-бітні значення (подвійні слова), оскільки регістри мають розмір 64 біти, але за потреби ви можете завантажувати та зберігати менші розміри для підвищення ефективності.

Категорії інструкцій та формат

Коли ви компілюєте код на Rust, C або асемблері, інструментарій видає потік інструкцій фіксованої ширини по 8 байтів, упакованих у секцію .text вашого ELF-файлу.

Кожна інструкція має послідовну структуру, яку віртуальна машина може декодувати за один прохід:

 
   1 byte    4 bits   4 bits     2 bytes         4 bytes
┌──────────┬────────┬────────┬──────────────┬──────────────────┐
│  opcode  │  dst   │  src   │   offset     │      imm         │
└──────────┴────────┴────────┴──────────────┴──────────────────┘
  • opcode: Визначає тип операції. Верхні 3 біти вибирають клас інструкції (арифметична, пам'ять, перехід, виклик, вихід), тоді як нижні 5 бітів визначають конкретний варіант (додавання, множення, завантаження, перехід-якщо-дорівнює).
  • dst: Номер регістра призначення (r0–r10), де зберігаються результати — результати арифметичних операцій, завантажені значення або повернені значення допоміжних функцій.
  • src: Вихідний регістр, що надає вхідні дані. Для двооперандної арифметики (add r1, r2) він надає друге значення. Для операцій з пам'яттю він може надавати базову адресу. Для варіантів з безпосереднім значенням (add r1, 10) ці 4 біти включаються в код операції.
  • offset: Невелике ціле число, яке змінює поведінку інструкції. Для завантаження/збереження воно додається до вихідної адреси, щоб досягти [src + offset]. Для переходів це відносна ціль переходу, виміряна в інструкціях.
  • imm: Поле безпосереднього значення. Арифметичні операції використовують його для констант (add r1, 42), CALL використовує його для номерів системних викликів (sol_log = 16), а операції з пам'яттю можуть розглядати його як абсолютний вказівник.

Категорії інструкцій

Різні типи інструкцій використовують ці поля особливим чином:

  • Переміщення даних: Переміщення значень між регістрами та пам'яттю:
sbpf
mov64 r1, 42           // Put immediate value 42 into r1
                       // opcode=move_imm, dst=1, src=unused, imm=42
 
ldxdw r0, [r10 - 8]    // Load 8 bytes from stack into r0  
                       // opcode=load64, dst=0, src=10, offset=-8, imm=unused
 
stxdw [r1 + 16], r0    // Store r0 to memory at [r1 + 16]
                       // opcode=store64, dst=1, src=0, offset=16, imm=unused
  • Арифметика: Виконання математичних операцій:
sbpf
add64 r1, r2           // r1 = r1 + r2
                       // opcode=add_reg, dst=1, src=2, offset=unused, imm=unused
 
add64 r1, 100          // r1 = r1 + 100  
                       // opcode=add_imm, dst=1, src=unused, offset=unused, imm=100
  • Керування потоком: Зміна послідовності виконання:
sbpf
ja +5                  // Jump forward 5 instructions unconditionally
                       // opcode=jump, dst=unused, src=unused, offset=5, imm=unused
 
jeq r1, r2, +3         // If r1 == r2, jump forward 3 instructions
                       // opcode=jump_eq_reg, dst=1, src=2, offset=3, imm=unused
 
jeq r1, 42, +3         // If r1 == 42, jump forward 3 instructions  
                       // opcode=jump_eq_imm, dst=1, src=unused, offset=3, imm=42

Кодування опкодів

Кодування опкодів містить багато інформації, окрім типу операції:

  • Клас інструкції: арифметична, пам'ять, перехід, виклик тощо.
  • Розмір операції: 32-бітні та 64-бітні операції
  • Тип джерела: регістр чи безпосереднє значення
  • Конкретна операція: додавання чи віднімання, завантаження чи збереження тощо.

Це створює окремі опкоди для варіантів інструкцій. Наприклад, add64 r1, r2 (джерело - регістр) використовує інший опкод, ніж add64 r1, 42 (безпосереднє джерело). Аналогічно, add64 та add32 мають різні опкоди для різних розмірів операцій.

Арифметичні операції додатково розрізняють знакові та беззнакові варіанти. udiv64 обробляє значення як беззнакові (від 0 до 18 квінтильйонів), тоді як sdiv64 обробляє знакові значення (від -9 квінтильйонів до +9 квінтильйонів).

Instruction Execution

Опкод визначає, як віртуальна машина інтерпретує решту полів.

Коли віртуальна машина зустрічає add64 r1, r2, вона зчитує опкод і розпізнає це як 64-бітну арифметичну операцію з використанням двох регістрів:

Поле dst вказує, що результат записується в r1, поле src визначає r2 як другий операнд, а поля offset та immediate ігноруються.

Для add64 r1, 42, опкод змінюється, щоб вказати на операцію з безпосереднім значенням. Тепер dst все ще вказує на r1, але src стає беззмістовним, а поле immediate надає другий операнд (42).

Операції з пам'яттю змістовно поєднують кілька полів:

Для ldxdw r1, [r2+8], опкод вказує на 64-бітне завантаження з пам'яті, dst отримує завантажене значення, src надає базову адресу, а offset (8) додається для створення кінцевої адреси r2 + 8.

Інструкції потоку керування слідують тому ж шаблону:

Коли ви пишете jeq r1, r2, +5, опкод кодує умовний перехід, порівнюючи два регістри. Якщо r1 дорівнює r2, віртуальна машина додає offset (5) до лічильника програми, переходячи вперед на 5 інструкцій.

Опкод визначає, які поля мають значення. Формат інструкції залишається постійним: опкод вказує, як інтерпретувати кожне поле, усуваючи складні режими адресації або особливі випадки.

Виклики функцій та системні виклики

Механізм виклику sBPF еволюціонував у різних версіях для кращої ясності та безпеки. До sBPF v3, call imm служив подвійним цілям: безпосереднє значення визначало, чи викликаєте ви внутрішню функцію, чи системний виклик.

Середовище виконання розрізняло їх на основі діапазону безпосередніх значень, при цьому номери системних викликів зазвичай були малими додатними цілими числами, як-от 16 для sol_log.

Починаючи з sBPF v3, інструкції розділилися для явної поведінки. call тепер обробляє внутрішні виклики функцій, використовуючи відносні зміщення, тоді як syscall imm явно викликає функції середовища виконання. Це розділення робить наміри байткоду зрозумілими та забезпечує кращу перевірку.

Непрямі виклики через callx також еволюціонували. У ранніх версіях цільовий регістр кодувався в полі безпосереднього значення, але починаючи з v2, він кодується в полі регістра джерела для узгодженості із загальним форматом інструкції.

Таблиця довідкових опкодів

Операції завантаження пам'яті

опкодМнемонікаОпис
lddwlddw dst, immЗавантаження 64-бітного безпосереднього значення (перший слот)
lddwlddw dst, immЗавантаження 64-бітного безпосереднього значення (другий слот)
ldxwldxw dst, [src + off]Завантаження слова з пам'яті
ldxhldxh dst, [src + off]Завантаження півслова з пам'яті
ldxbldxb dst, [src + off]Завантаження байта з пам'яті
ldxdwldxdw dst, [src + off]Завантаження подвійного слова з пам'яті

Операції зберігання в пам'яті

opcodeМнемонікаОпис
stwstw [dst + off], immЗберегти слово безпосередньо
sthsth [dst + off], immЗберегти півслово безпосередньо
stbstb [dst + off], immЗберегти байт безпосередньо
stdwstdw [dst + off], immЗберегти подвійне слово безпосередньо
stxwstxw [dst + off], srcЗберегти слово з регістра
stxhstxh [dst + off], srcЗберегти півслово з регістра
stxbstxb [dst + off], srcЗберегти байт з регістра
stxdwstxdw [dst + off], srcЗберегти подвійне слово з регістра

Арифметичні операції (64-біт)

opcodeМнемонікаОпис
add64add64 dst, immДодати безпосереднє значення
add64add64 dst, srcДодати з регістра
sub64sub64 dst, immВідняти безпосереднє значення
sub64sub64 dst, srcВідняти з регістра
mul64mul64 dst, immПомножити на безпосереднє значення
mul64mul64 dst, srcПомножити на значення з регістра
div64div64 dst, immПоділити на безпосереднє значення (беззнакове)
div64div64 dst, srcПоділити на значення з регістра (беззнакове)
sdiv64sdiv64 dst, immПоділити на безпосереднє значення (знакове)
sdiv64sdiv64 dst, srcПоділити на значення з регістра (знакове)
mod64mod64 dst, immЗалишок від ділення на безпосереднє значення (беззнакове)
mod64mod64 dst, srcЗалишок від ділення на значення з регістра (беззнакове)
smod64smod64 dst, immЗалишок від ділення на безпосереднє значення (знакове)
smod64smod64 dst, srcЗалишок від ділення на значення з регістра (знакове)
neg64neg64 dstЗаперечення

Арифметичні операції (32-біт)

opcodeМнемонікаОпис
add32add32 dst, immДодати безпосереднє значення (32-біт)
add32add32 dst, srcДодати з регістра (32-біт)
sub32sub32 dst, immВідняти безпосереднє значення (32-біт)
sub32sub32 dst, srcВідняти з регістра (32-біт)
mul32mul32 dst, immПомножити на безпосереднє значення (32-біт)
mul32mul32 dst, srcПомножити на значення з регістра (32-біт)
div32div32 dst, immПоділити на безпосереднє значення (32-біт)
div32div32 dst, srcПоділити на значення з регістра (32-біт)
sdiv32sdiv32 dst, immПоділити на безпосереднє значення (знакове 32-біт)
sdiv32sdiv32 dst, srcПоділити на значення з регістра (знакове 32-біт)
mod32mod32 dst, immЗалишок від ділення на безпосереднє значення (32-біт)
mod32mod32 dst, srcЗалишок від ділення на значення з регістра (32-біт)
smod32smod32 dst, immЗалишок від ділення на безпосереднє значення (знакове 32-біт)
smod32smod32 dst, srcЗалишок від ділення на значення з регістра (знакове 32-біт)

Логічні операції (64-біт)

opcodeМнемонікаОпис
or64or64 dst, immПобітове АБО з константою
or64or64 dst, srcПобітове АБО з регістром
and64and64 dst, immПобітове І з константою
and64and64 dst, srcПобітове І з регістром
lsh64lsh64 dst, immЗсув вліво на константу
lsh64lsh64 dst, srcЗсув вліво на регістр
rsh64rsh64 dst, immЗсув вправо на константу
rsh64rsh64 dst, srcЗсув вправо на регістр
xor64xor64 dst, immПобітове виключне АБО з константою
xor64xor64 dst, srcПобітове виключне АБО з регістром
mov64mov64 dst, immПереміщення константи
mov64mov64 dst, srcПереміщення регістра
arsh64arsh64 dst, immАрифметичний зсув вправо на константу
arsh64arsh64 dst, srcАрифметичний зсув вправо на регістр

Логічні операції (32-біт)

opcodeМнемонікаОпис
or32or32 dst, immПобітове АБО з константою (32-біт)
or32or32 dst, srcПобітове АБО з регістром (32-біт)
and32and32 dst, immПобітове І з константою (32-біт)
and32and32 dst, srcПобітове І з регістром (32-біт)
lsh32lsh32 dst, immЗсув вліво на константу (32-біт)
lsh32lsh32 dst, srcЗсув вліво на регістр (32-біт)
rsh32rsh32 dst, immЗсув вправо на константу (32-біт)
rsh32rsh32 dst, srcЗсув вправо на регістр (32-біт)
xor32xor32 dst, immПобітове виключне АБО з константою (32-біт)
xor32xor32 dst, srcПобітове виключне АБО з регістром (32-біт)
mov32mov32 dst, immПереміщення константи (32-біт)
mov32mov32 dst, srcПереміщення регістра (32-біт)
arsh32arsh32 dst, immАрифм. зсув вправо на константу (32-біт)
arsh32arsh32 dst, srcАрифм. зсув вправо на регістр (32-біт)

Операції керування потоком

opcodeМнемонікаОпис
jaja offБезумовний перехід (jump 0 = перехід до наступного)
jeqjeq dst, imm, offПерехід, якщо дорівнює безпосередньому значенню
jeqjeq dst, src, offПерехід, якщо дорівнює регістру
jgtjgt dst, imm, offПерехід, якщо більше безпосереднього значення (беззнакове)
jgtjgt dst, src, offПерехід, якщо більше регістра (беззнакове)
jgejge dst, imm, offПерехід, якщо більше або дорівнює безпосередньому значенню (беззнакове)
jgejge dst, src, offПерехід, якщо більше або дорівнює регістру (беззнакове)
jsetjset dst, imm, offПерехід, якщо біт встановлено (безпосередня маска)
jsetjset dst, src, offПерехід, якщо біт встановлено (маска регістра)
jnejne dst, imm, offПерехід, якщо не дорівнює безпосередньому значенню
jnejne dst, src, offПерехід, якщо не дорівнює регістру
jsgtjsgt dst, imm, offПерехід, якщо більше безпосереднього значення (знакове)
jsgtjsgt dst, src, offПерехід, якщо більше регістра (знакове)
jsgejsge dst, imm, offПерехід, якщо більше або дорівнює безпосередньому значенню (знакове)
jsgejsge dst, src, offПерехід, якщо більше або дорівнює регістру (знакове)
jltjlt dst, imm, offПерехід, якщо менше безпосереднього значення (беззнакове)
jltjlt dst, src, offПерехід, якщо менше регістра (беззнакове)
jlejle dst, imm, offПерехід, якщо менше або дорівнює безпосередньому значенню (беззнакове)
jlejle dst, src, offПерехід, якщо менше або дорівнює регістру (беззнакове)
jsltjslt dst, imm, offПерехід, якщо менше безпосереднього значення (знакове)
jsltjslt dst, src, offПерехід, якщо менше регістра (знакове)
jslejsle dst, imm, offПерехід, якщо менше або дорівнює безпосередньому значенню (знакове)
jslejsle dst, src, offПерехід, якщо менше або дорівнює регістру (знакове)

Операції виклику функцій

опкодМнемонікаОпис
callcall imm або syscall immВиклик функції або системний виклик
callxcallx immНепрямий виклик (регістр у полі imm)
exitexit або returnПовернення з функції

Операції обміну байтів

опкодМнемонікаОпис
bebe dst, immОбмін байтів (16, 32 або 64 біт)
lele dst, immПеретворення у порядок little endian (застаріло)
Blueshift © 2025Commit: 6d01265