Машинный код, ассемблер и язык высокого уровня

Урок объясняет, что процессор понимает только числа, а ассемблер — это человекочитаемая запись этих чисел.

Ассемблер — низкоуровневый язык, где почти каждая строка соответствует одной машинной команде процессора.

Зачем вообще спускаться так низко

Когда вы пишете 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.
Проверьте себя
1. Что такое ассемблер по отношению к машинному коду?
AСовершенно другой язык, не связанный с командами процессора
BЧеловекочитаемая запись машинных команд, почти один к одному
CБиблиотека готовых функций для C
DГрафический редактор схем процессора
2. Какой регистр хранит адрес следующей исполняемой команды?
ARAX
BRSP
CRIP
DRFLAGS
3. Почему ассемблер стоит изучать сегодня?
AЧтобы писать на нём все программы вместо C
BПотому что он всегда быстрее любого компилятора
CЧтобы понимать работу машины: падения, отладку, безопасность, производительность
DПотому что он одинаков для всех процессоров