Курс ЛР9 · Місія 9
Академічна↔ Сюжет С.І.Д.СРС до ЛР⌨️ команди 10 · 1 нова

Лабораторна робота № 9. Той самий алгоритм у двох асемблерах

Мета

Навчитися компілювати один і той самий алгоритм у два різних цільові набори інструкцій — x86-64 та WebAssembly — і порівнювати результат: розмір бінарника, кількість і склад згенерованих інструкцій, накладні витрати на виклик функції та результати дизасемблювання.

📚 ТЕОРЕТИЧНІ ВІДОМОСТІ

Теоретичні відомості

Той самий алгоритм, написаний однією мовою вищого рівня (наприклад, C), компілятор може перетворити на послідовність інструкцій для абсолютно різних виконавчих машин. Для x86-64 (лабораторна № 8 попереднього модуля курсу асемблера) це реальний фізичний процесор з фіксованим набором іменованих регістрів і угодою виклику System V AMD64 ABI. Для WebAssembly — стекова віртуальна машина з операційним стеком значень і структурованим керуванням потоком (block/loop/br_if).

Порівняння двох виводів компіляції показує, що саме є «істотним» для алгоритму (кількість операцій, порядок обчислень), а що — деталлю конкретної цільової архітектури (як саме зберігаються проміжні значення: у регістрах чи на стеці, як організований пролог/епілог функції).

Накладні витрати на виклик функції (call overhead) — різниця між «чистою» роботою функції та вартістю самого механізму виклику: збереження регістрів, побудова кадру стека, передача аргументів. У x86-64 це пролог/епілог (push %rbp, mov %rsp,%rbp, pop %rbp) плюс збереження callee-saved регістрів. У WASM виклик функції теж має вартість — рушій перевіряє типи сигнатури і керує окремим call-стеком, захищеним від переповнення (на відміну від необмеженого стека нативного коду).

Дизасемблювання — процес відновлення асемблерного представлення з бінарного файлу. Для x86-64 інструмент — objdump -d, для WebAssembly — wasm-objdump -d (з пакета WABT) або wasm2wat для повернення до текстового формату. Дизасемблювання використовується для аудиту безпеки: перевірки, що скомпільований код відповідає вихідному, і пошуку небезпечних патернів роботи з пам'яттю (наприклад, відсутність перевірки меж масиву).

Розмір скомпільованого файлу сам собою не є мірою швидкодії: WASM-модулі часто компактніші за об'єктні файли x86-64 того самого алгоритму через щільніше кодування інструкцій, а не через вищу продуктивність виконання.

Обладнання та програмне забезпечення

Порядок виконання

  1. Обрати алгоритм за варіантом (НСД, сума цифр числа, факторіал) з starter/README.md. Очікуваний результат: відома сигнатура функції та її логіка.
  2. Скомпілювати алгоритм у x86-64: gcc -O0 -c algo.c -o algo_x86.o, отримати дизасемблювання objdump -d algo_x86.o > x86.txt. Очікуваний результат: об'єктний файл і непорожній x86.txt з міткою функції.
  3. Скомпілювати той самий алгоритм у WebAssembly (clang --target=wasm32 ... або еквівалент з starter/README.md), отримати .wasm. Очікуваний результат: валідний algo.wasm з експортованою функцією.
  4. Дизасемблювати WASM: wasm-objdump -d algo.wasm > wasm.txt. Очікуваний результат: непорожній wasm.txt.
  5. Порівняти розміри файлів (ls -la algo_x86.o algo.wasm) і кількість інструкцій у кожному дизасемблюванні.
  6. Заповнити таблицю порівняння (шаблон у starter/README.md): кількість інструкцій, розмір у байтах, наявність пролог/епілог-патернів.
  7. Для рівня 2 — виміряти чи оцінити накладні витрати виклику функції в обох середовищах.
  8. Для рівня 3 — переглянути дизасемблювання на предмет патернів роботи з пам'яттю без перевірки меж.
РІВЕНЬ 1 · «3» · ПЕРШОКУРСНИК

Рівень 1 (оцінка 3)

Скомпілювати один алгоритм у x86-64 та WebAssembly, порівняти розмір і згенеровані інструкції за таблицею з comparison.md.

РІВЕНЬ 2 · «4» · МАГІСТР

Рівень 2 (оцінка 4, захист)

Проаналізувати накладні витрати на виклик функцій у обох середовищах: показати пролог/епілог у x86.txt і обґрунтувати аналогічну вартість у WASM. На захисті — пояснити різницю усно.

РІВЕНЬ 3 · «5» · ЛЕГЕНДА

Рівень 3 (оцінка 5, розширення)

Дизасемблювати отримані бінарники для пошуку потенційних вразливостей в управлінні пам'яттю (доступ до пам'яті без перевірки меж) і задокументувати висновок у comparison.md, навіть якщо він негативний.

🧪 РОЗІБРАНИЙ ПРИКЛАД

Розібраний приклад

Алгоритм НСД Евкліда (algo.c):

int gcd(int a, int b) {
    while (b != 0) {
        int t = b;
        b = a % b;
        a = t;
    }
    return a;
}

Дизасемблювання x86-64 (gcc -O0, спрощено):

gcd:
    push   %rbp
    mov    %rsp,%rbp
    mov    %edi,-0x14(%rbp)
    mov    %esi,-0x18(%rbp)
.loop:
    cmpl   $0x0,-0x18(%rbp)
    je     .end
    mov    -0x18(%rbp),%eax
    mov    %eax,-0x1c(%rbp)
    mov    -0x14(%rbp),%eax
    cltd
    idivl  -0x18(%rbp)
    mov    %edx,-0x18(%rbp)
    mov    -0x1c(%rbp),%eax
    mov    %eax,-0x14(%rbp)
    jmp    .loop
.end:
    mov    -0x14(%rbp),%eax
    pop    %rbp
    ret

Дизасемблювання WebAssembly того самого алгоритму (wasm-objdump -d algo.wasm) — на відміну від objdump, тут немає гарного .wat-тексту, лише сирі байти опкодів поруч із мнемонікою і пронумерованими (без імен) локальними змінними (0=a, 1=b, 2=t):

000023 func[0] <gcd>:
 000024: 02 40                      | block
 000026: 03 40                      | loop
 000028: 20 01                      | local.get 1
 00002a: 45                         | i32.eqz
 00002b: 0d 01                      | br_if 1
 00002d: 20 01                      | local.get 1
 00002f: 21 02                      | local.set 2
 000031: 20 00                      | local.get 0
 000033: 20 01                      | local.get 1
 000035: 6f                         | i32.rem_s
 000036: 21 01                      | local.set 1
 000038: 20 02                      | local.get 2
 00003a: 21 00                      | local.set 0
 00003c: 0c 00                      | br 0
 00003e: 0b                         | end
 00003f: 0b                         | end
 000040: 20 00                      | local.get 0
 000042: 0b                         | end

Той самий цикл і та сама операція a % b — але умовний перехід je/jmp між мітками x86-64 замінено структурованим block/loop/br_if у WASM. Команди порівняння розмірів: ls -la algo_x86.o algo.wasm та wc -l x86.txt wasm.txt.

❓ КОНТРОЛЬНІ ПИТАННЯ

Контрольні питання

  1. Чому той самий алгоритм дає різну кількість інструкцій у x86-64 та WASM?
  2. Що саме входить у «накладні витрати виклику функції» і чому вони не дорівнюють нулю?
  3. Чим call-стек WASM захищений краще за стек нативного процесу?
  4. Які інструменти дозволяють дизасемблювати x86-64-об'єктний файл, а які — WASM-модуль?
  5. Що означає, якщо розмір .wasm-файлу менший за розмір .o-файлу для того самого алгоритму?
  6. Наведіть приклад патерну в дизасемблюванні, який може вказувати на відсутність перевірки меж масиву.
  7. Чому WASM-модуль неможливо (у стандартній моделі) примусити виконати jmp за межі функції так, як це теоретично можливо в нативному коді?

Парні теми СРС

Критерії оцінювання та форма звіту

Здається тег submit/lab09 з вихідним кодом алгоритму, обома дизасемблюваннями (x86.txt, wasm.txt) і заповненою таблицею порівняння (comparison.md). Автотести рівня 1 (див. autograder/README.md) перевіряють: наявність усіх артефактів, що обидва бінарники успішно скомпільовані та відповідають вихідному алгоритму (виконуються на еталонних вхідних даних з коректним результатом), що таблиця порівняння заповнена (розмір, кількість інструкцій — непорожні числові значення). Рівні 2 і 3 захищаються усно: студент пояснює знайдені відмінності у накладних витратах виклику та показує (якщо є) підозрілі патерни в дизасемблюванні. Звіт додає скріншоти обох дизасемблювань і заповненої таблиці.

⌨️ КОМАНДИ НА ЦІЙ СТОРІНЦІ 10 · 1 нова

Кожна команда терміналу з цієї сторінки — одним реченням. Позначка нове — команда зустрічається в курсі вперше; далі вважаємо її знайомою.

objdump
Дизасемблер: objdump -d f.o показує машинний код як інструкції з адресами й опкодами (-M intel — синтаксис Intel).
wasm-objdump
З WABT: дизасемблер WebAssembly — -d показує опкоди й стекові інструкції, -x — секції та експорти.
wasm2wat
З WABT: зворотне — бінарний .wasm назад у читабельний WAT.
gcc
Компілятор C (GNU); gcc -O0 -c f.c -o f.o — лише скомпілювати в об'єктний файл без оптимізацій.
wat2wasm
З WABT: перекладає текстовий формат WAT у бінарний модуль .wasm (wat2wasm m.wat -o m.wasm).
rustup target
Додає ціль компіляції для іншої платформи (rustup target add wasm32-unknown-unknown — щоб збирати у WebAssembly).
rustup
Встановлювач і менеджер версій Rust: тулчейни, цілі (targets), компоненти.
wc
Рахує рядки, слова й байти; ... | wc -l — скільки рядків видала попередня команда.
ls
Показує список файлів у каталозі; ls -la — з правами, власником, розміром і прихованими файлами.
clangнове
Компілятор C/C++ (LLVM); з --target=wasm32 збирає той самий C у WebAssembly — щоб порівняти з x86-64.

💡 Прочитати README прямо в терміналі: cat README.md (виведе весь файл) або less README.md (посторінково; вихід — клавіша q).
Довідка з будь-якої команди: man curl (повний посібник, вихід — q) або коротко curl --help; для підкоманд — docker run --help, cargo build --help.