Машинный код, ассемблер и язык высокого уровня
Урок объясняет, что процессор понимает только числа, а ассемблер — это человекочитаемая запись этих чисел.
Ассемблер — низкоуровневый язык, где почти каждая строка соответствует одной машинной команде процессора.
Зачем вообще спускаться так низко
Когда вы пишете print("Привет") на Python или x = a + b на C, вы общаетесь с машиной через множество слоёв. Процессор же не знает ни про print, ни про переменные. Он умеет крошечный набор действий: положить число в регистр, сложить два числа, сравнить их, перейти на другую команду. Вся мощь компьютера вырастает из миллиардов таких элементарных шагов в секунду.
Учить ассемблер стоит не ради того, чтобы писать на нём большие программы — это медленно и тяжело. Его учат, чтобы понимать: почему программа падает с «segmentation fault», почему один цикл быстрее другого, что показывает отладчик, как устроена безопасность. После этого курса слова «регистр», «стек» и «дизассемблер» перестанут быть магией.
Три уровня одной программы
Рассмотрим одну и ту же идею «сложить 2 и 3» на трёх уровнях.
Высокий уровень (C): читается как математика.
int main() {
int x = 2 + 3;
return x;
}Ассемблер (x86-64, NASM): те же действия, но по шагам процессора.
mov rax, 2 ; положить 2 в регистр rax
add rax, 3 ; прибавить 3, теперь в rax число 5
ret ; вернуться из функцииМашинный код: то, что реально лежит в памяти, — просто байты.
48 C7 C0 02 00 00 00 ; это и есть mov rax, 2
48 83 C0 03 ; это add rax, 3
C3 ; это retАссемблер — это ровно та же программа, что и набор байтов, но записанная буквами. Программа-ассемблер (например, NASM) переводит текст в байты, а дизассемблер делает обратное.
Как работает под капотом
Процессор крутит вечный цикл из трёх шагов, который называют fetch–decode–execute (выборка–декодирование–исполнение):
+------------------------------------------+
| 1. FETCH взять команду по адресу RIP |
| 2. DECODE понять, что это за команда |
| 3. EXECUTE выполнить её |
| RIP сдвигается на следующую команду |
+------------------------------------------+
^ |
+----------------------------------+Специальный регистр RIP (instruction pointer) хранит адрес следующей команды. После каждой команды он сдвигается вперёд — или прыгает в другое место, если встретилась команда перехода. Именно так из линейного списка байтов получаются циклы и ветвления.
Частые ошибки новичка
- «Ассемблер один на все процессоры». Нет: у x86-64, ARM и RISC-V разные наборы команд. Мы учим x86-64 — то, что стоит в большинстве ноутбуков и серверов.
- Путать синтаксисы. Есть Intel-синтаксис (его использует NASM,
mov rax, 2— «куда, что») и AT&T-синтаксис (movq $2, %rax— наоборот). В курсе только Intel/NASM. - Думать, что ассемблер всегда быстрее. Современный компилятор C обычно пишет машинный код лучше человека. Ассемблер ценен пониманием, а не скоростью набора.
Итог
- Процессор исполняет только машинный код — числа в памяти.
- Ассемблер — человекочитаемая запись этих чисел, почти один к одному.
- Цикл fetch–decode–execute и регистр RIP управляют порядком исполнения.
- Мы учим x86-64 в синтаксисе NASM под Linux.