Секрети

Секрети

Навчальна вправа

Вступ

Робота з бітами

Кожен біт цілого числа можна використати, щоб зберегти двійкове значення. Оскільки багато ситуацій повʼязані з двійковою інформацією, як-от правда чи неправда, включення чи виключення, увімкнено чи вимкнено, двійкове подання N-бітного цілого числа дає компактний спосіб закодувати двійковий стан N елементів. Саме тому вміння працювати з бітами й байтами є необхідним в асемблері. Набір інструкцій x86-64 пропонує широкий вибір інструкцій для побітових операцій.

Робота з окремими бітами

Ці інструкції працюють з окремими бітами в операнді.

Усі вони приймають два операнди, другий указує індекс біта, з яким працюють у першому операнді. Усі вони копіюють вибраний біт у прапорець перенесення (CF).

Назва Опис
bt копіює біт у CF, не змінюючи жодного операнда
bts копіює біт у CF і встановлює його в операнді призначення
btr копіює біт у CF і скидає його в операнді призначення
btc копіює біт у CF і доповнює (інвертує) його в операнді призначення

Побітові операції

Побітові операції виконуються над усіма бітами операнда.

Для кожної з них є інструкція з тією ж назвою, що й виконувана побітова операція:

Назва Опис
and 1, якщо обидва біти дорівнюють 1
or 1, якщо принаймні один із бітів дорівнює 1
xor 1, якщо біти різняться
not 1, якщо біт був 0; 0, якщо біт був 1

Більшість із них приймають два операнди, виконують побітову операцію над обома й зберігають результат в операнді призначення. Виняток - not, яка приймає лише один операнд призначення.

Маски

Коли ми тлумачимо одиницю та нуль як включення та виключення відповідно, ціле число називають бітовою маскою (або просто маскою).

Бітова маска «відсіює» елементи, бо нуль у i-му біті виключає i-й елемент, а одиниця включає його. Також ми часто використовуємо бітову маску, щоб включити певні біти цілого числа й виключити інші.

Наприклад, нехай A буде цілим числом, двійкове подання якого таке:

індекс 7 6 5 4 3 2 1 0
біти 1 0 0 1 0 1 0 1

Так само нехай M буде цілим числом, двійкове подання якого таке:

індекс 7 6 5 4 3 2 1 0
біти 0 0 0 0 1 1 0 1

Обидва - 8-бітні цілі числа. У цьому випадку можна сказати, що M вибирає біти 0, 2 і 3 числа A, а решту виключає.

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

  • Щоб скинути біти A, які не вибрані маскою M, виконаймо побітове AND: A AND M.
  • Щоб встановити біти A, вибрані маскою M, виконаймо побітове OR: A OR M.

Інструкція test

Інструкція test виконує побітове AND між обома операндами й встановлює прапорці відповідно до результату.

Нехай A буде першим операндом, а B - другим:

прапорець встановлюється, коли
CF завжди скидається
ZF A AND B == 0
SF знаковий біт A AND B встановлено
OF завжди скидається

Ця інструкція приймає два операнди й оновлює прапорці, але не змінює свої операнди.

Операції зсуву

Ці інструкції переміщують біти в операнді призначення на кількість позицій, указану другим операндом. Другий операнд має бути сталим числом (immediate) або регістром cl (наймолодші 8 бітів rcx).

Назва Опис
shl/sal Зсуває біти ліворуч
shr/sar Зсуває біти праворуч

Зауважмо, що лічильник у другому операнді маскується до 5 бітів, або 6 бітів, якщо операнд призначення 64-бітний. Будь-які біти після цього фактично ігноруються. Це означає, що максимальний зсув - 31, або 63 з 64-бітним операндом.

shl / sal

І shl, і sal виконують ту саму операцію, одна з них - псевдонім іншої.

Щоразу, коли виконується зсув ліворуч, біти, розташовані ближче до кінця послідовності, ніж довжина зсуву, спершу переміщуються в CF, а потім відкидаються. Натомість на початок додається стільки нових скинутих бітів, скільки становить довжина зсуву.

Оскільки кожен біт у цілому числі представляє степінь 2, зсув ліворуч на n позицій приводить до множення цілого числа на 2ⁿ.

shr / sar

Є дві інструкції для переміщення бітів праворуч: shr і sar.

Коли використовується будь-яка з цих двох інструкцій, біти, розташовані ближче до початку послідовності, ніж довжина зсуву, спершу переміщуються в CF, а потім відкидаються. Натомість у кінець додається стільки нових бітів, скільки становить довжина зсуву.

Різниця між ними в тому, що shr додає 0 бітів на лівому кінці, а sar додає 1, якщо найстарший біт був встановлений, і 0 в іншому разі. Це означає, що sar зберігає знак під час зсуву знакового цілого числа.

Оскільки кожен біт у цілому числі представляє степінь 2, зсув праворуч на n позицій за допомогою shr приводить до беззнакового ділення на 2ⁿ.

Так само зсув праворуч на n позицій за допомогою sar приводить до знакового ділення на 2ⁿ.

Операції обертання

Ці інструкції переміщують біти в операнді призначення на кількість позицій, указану другим операндом. Другий операнд має бути сталим числом (immediate) або регістром cl (наймолодші 8 бітів rcx).

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

Назва Опис
rol Обертає біти ліворуч
ror Обертає біти праворуч

Зауважмо, що лічильник у другому операнді маскується до 5 бітів, або 6 бітів, якщо операнд призначення 64-бітний. Будь-які біти після цього фактично ігноруються. Це означає, що максимальне обертання - 31, або 63 з 64-бітним операндом.

Інші інструкції для роботи з бітами

Є й інші корисні інструкції для роботи з бітами:

Назва Опис
popcnt Підраховує кількість встановлених бітів
bsr Повертає індекс найстаршого встановленого біта. Якщо жоден біт не встановлено, результат невизначений
bsf Повертає індекс наймолодшого встановленого біта. Якщо жоден біт не встановлено, результат невизначений

Усі ці інструкції працюють із двома 16-бітними, 32-бітними або 64-бітними операндами. Їх не можна використовувати з 8-бітними операндами.

Вказівки

Наш друг щойно надіслав нам повідомлення з важливою таємницею. Щоб його не змогли легко прочитати інші, повідомлення зашифрували, виконавши низку операцій над бітами. Нам потрібно буде написати методи, які допоможуть розшифрувати повідомлення.

Note

Ось інструкції для одного біта, згадані в цьому концепті:

Name Description
bt копіює біт у CF, не змінюючи жодного операнда
bts копіює біт у CF і встановлює його в операнді призначення
btr копіює біт у CF і скидає його в операнді призначення
btc копіює біт у CF і доповнює (перевертає) його в операнді призначення

Ось побітові інструкції, згадані в цьому концепті:

Name Description
and 1, якщо обидва біти дорівнюють 1
or 1, якщо принаймні один із бітів дорівнює 1
xor 1, якщо біти різняться
not 1, якщо біт був 0; 0, якщо біт був 1

Ось інструкції зсуву, згадані в цьому концепті:

Name Description
shl/sal зсуває біти вліво
shr/sar зсуває біти вправо

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

Name Description
rol обертає біти вліво
ror обертає біти вправо

Ось різні інструкції, згадані в цьому концепті:

Name Description
popcnt рахує кількість установлених бітів
bsr отримує індекс найстаршого встановленого біта. Якщо жоден біт не встановлено, результат не визначено
bsf отримує індекс наймолодшого встановленого біта. Якщо жоден біт не встановлено, результат не визначено

1. Видобути маску

Повідомлення закодовано в 16-бітному цілому числі. Однак із них 8 старших бітів насправді не є частиною повідомлення, а маскою, яку потрібно використати для розшифрування.

Реалізуйте функцію extract_higher_bits, яка приймає 16-бітне ціле число і повертає його 8 старших бітів.

extract_higher_bits(0b1010010011000101)
// => 0b10100100

2. Видобути повідомлення

Мало вміти видобути маску, слід також відокремити саме повідомлення.

Реалізуйте функцію extract_lower_bits, яка приймає 16-бітне ціле число і повертає його 8 молодших бітів.

extract_lower_bits(0b1010010011000101);
// => 0b11000101

3. Видобути надлишкові біти

Деякі біти встановлені як у повідомленні, так і в масці. Це дуже важлива інформація, яка знадобиться пізніше.

Реалізуйте функцію extract_redundant_bits, яка приймає 16-бітне ціле число, що кодує і повідомлення, і маску, і повертає 8-бітне ціле число, у якому встановлено лише надлишкові біти. Біт у поверненому числі має бути встановлений в 1 там, де він також дорівнює 1 і в повідомленні, і в масці. Усі інші біти мають бути скинуті.

extract_redundant_bits(0b1010010011000101);
// => 0b10000100

4. Установити всі біти повідомлення

Далі, деякі біти потрібно встановити в 1 в повідомленні, відповідно до маски.

Реалізуйте функцію set_message_bits, яка приймає 16-бітне ціле число, що кодує і повідомлення, і маску, і повертає результат установлення бітів повідомлення в 1. Біт повідомлення має бути встановлений в 1 там, де біт у масці дорівнює 1. Усі інші біти мають залишитися без змін, тобто встановленими, якщо вони вже були встановлені, і скинутими, якщо вже були скинуті.

set_message_bits(0b1010010011000101);
// => 0b11100101

5. Обернути приватний ключ

Одна частина головоломки в повідомленні явно не вказана: 16-бітне число 0b1011001100111100. Це число - спільний приватний ключ, і його потрібно використати, щоб допомогти розшифрувати повідомлення.

Щоб це зробити, спершу потрібно обернути біти приватного ключа вліво на певну кількість позицій. Кількість позицій дорівнює кількості надлишкових бітів, установлених і в повідомленні, і в масці.

Реалізуйте функцію rotate_private_key, яка приймає 16-бітне ціле число, що кодує і повідомлення, і маску, і повертає результат обертання приватного ключа. Цей результат - 16-бітне ціле число.

rotate_private_key(0b1010010011000101);
// => 0b1100110011110010
Note

NASM (The Netwide Assembler, асемблер, який використовує цей трек) підтримує константи у двійковому форматі з префіксом 0b. Він також дозволяє використовувати символ підкреслення (_) як розділювач у константі для кращої читабельності:

PRIVATE_KEY equ 0b1011_0011_0011_1100

6. Відформатувати приватний ключ

Щоб його можна було використати для розшифрування, приватний ключ потрібно відформатувати так, щоб відокремити потрібні біти.

Щоб повністю відформатувати приватний ключ, потрібно:

  • Обернути його.
  • Відокремити молодшу 8-бітну частину оберненого приватного ключа, яка є базовим значенням.
  • Відокремити старшу 8-бітну частину оберненого приватного ключа, яка є маскою, що застосовується до базового значення.
  • Перевернути біти в базовому значенні, які також установлені в масці.
  • Перевернути всі біти в результаті.

Перевернутий біт дорівнює 1, якщо він був 0, і 0, якщо він був 1.

Реалізуйте функцію format_private_key, яка приймає 16-бітне ціле число, що кодує і повідомлення, і маску, і повертає повністю відформатований 8-бітний приватний ключ.

format_private_key(0b1010010011000101);
// => 0b11000001

7. Завершити розшифрування

Коли в нас уже є повідомлення з усіма потрібними встановленими бітами та відформатований приватний ключ, настав час поєднати їх, щоб отримати підсумкове повідомлення.

Підсумкове повідомлення - це 16-бітне ціле число, у якому:

  • Старші 8 бітів заповнюються відформатованим приватним ключем.
  • Молодші 8 бітів заповнюються повідомленням після встановлення всіх потрібних бітів.

Реалізуйте функцію decrypt_message, яка приймає 16-бітне ціле число, що кодує і повідомлення, і маску, і повертає 16-бітне ціле число з повністю розшифрованим повідомленням.

Ця функція має використовувати відформатований приватний ключ, який ми отримуємо за допомогою format_private_key, а також повідомлення з усіма потрібними встановленими бітами, отримане за допомогою set_message_bits.

decrypt_message(0b1010010011000101);
// => 0b1100000111100101
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
x86-64 Assembly Exercism

Час розпочати Секрети?

Зареєструйтеся на Exercism, щоб вивчати й опановувати x86-64 Assembly, а також 22 концепції130 вправ та справжнє наставництво від людей, і все це безкоштовно.