Язык ассемблера - Регистры

Регистр — это высокоскоростной блок хранения внутри процессора, и наиболее часто обрабатываемый объект в программировании на языке ассемблера. Понимание регистров — ключевой первый шаг к хорошему изучению языка ассемблера.


Что такое регистр

Регистры встроены внутрь кристалла процессораСверхвысокоскоростное малое запоминающее устройство, используемое для временного хранения инструкций, данных и адресов.

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

В языке ассемблера подавляющее большинство операций вращается вокруг регистров — данные загружаются из памяти в регистры, операции выполняются в регистрах, а затем результаты сохраняются обратно в память.

Можно рассматривать регистры как «верстак» процессора. Инструменты на верстаке доступны в любой момент, тогда как память похожа на склад, к которому нужно идти, чтобы взять и положить предметы.


Классификация 32-битных регистров x86

32-битная архитектура x86 предоставляет множество типов регистров, каждый из которых имеет свое назначение. На следующем рисунке показана полная классификация регистров:

x86 32位寄存器分类概览

Ниже мы подробно рассмотрим каждый тип регистров:


Регистры общего назначения

Регистры общего назначенияЭто наиболее часто используемые регистры, применяемые для хранения данных операций и временных результатов.

x86 предоставляет 8 32-битных регистров общего назначения:

32-битный16-битныйМладшие 8 битСтаршие 8 бит (младших 16 бит)Основное назначение
EAXAXALAHАккумулятор, хранит возвращаемые значения функций и результаты арифметических операций
EBXBXBLBHБазовый регистр, часто используется для хранения базовых адресов памяти
ECXCXCLCHСчётчик, часто используется для подсчёта циклов и сдвигов
EDXDXDLDHРегистр данных, хранит старшие результаты умножения и деления
ESISISIL-Регистр индекса источника, адрес источника для строковых операций
EDIDIDIL-Регистр индекса назначения, адрес назначения для строковых операций
EBPBPBPL-Указатель базы, указывает на основание текущего кадра стека
ESPSPSPL-Указатель стека, всегда указывает на вершину стека

Соглашение об именовании: префикс E означает Extended (расширенный до 32 бит), а суффикс X указывает, что его можно разбить на старший и младший байты.

Пример

; Путь к файлу: register_parts.asm
; Демонстрация доступа к различным частям регистров

section .text
    global _start

_start:
    mov eax, 0x12345678     ; Полный 32-битный регистр
    ; В этот момент: EAX = 0x12345678
    ; AX = 0x5678 (младшие 16 бит)
    ; AH = 0x56 (старшие 8 бит, то есть старшие 8 бит AX)
    ; AL = 0x78 (младшие 8 бит)

    mov ax, 0xAABB          ; Изменить AX (младшие 16 бит)
    ; В этот момент: EAX = 0x1234AABB (старшие 16 бит остаются неизменными!)
    ; AX = 0xAABB
    ; AL = 0xBB

    mov al, 0xCC            ; Изменить AL (самые младшие 8 бит)
    ; В этот момент: EAX = 0x1234AACC (изменились только младшие 8 бит)
    ;       AX  = 0xAACC
    ;       AL  = 0xCC

    mov eax, 1
    mov ebx, 0
    int 0x80

При изменении младших 16 бит 32-битного регистра (например, AX) старшие 16 бит остаются неизменными. Однако, когда 32-битный регистр используется как операнд назначения, все 32 бита перезаписываются. Это распространённый источник ошибок у начинающих.


Сегментные регистры

Сегментные регистры используются для указания текущего используемого сегмента памяти:

РегистрNameНазначение
CSРегистр сегмента кодаУказывает на сегмент, в котором находится текущая инструкция
DSРегистр сегмента данныхУказывает на сегмент, в котором расположены данные
SSРегистр сегмента стекаУказывает на сегмент, в котором расположен стек
ESРегистр дополнительного сегментаДополнительный сегмент данных
FSРегистр дополнительного сегментаОбщего назначения, часто используется для локального хранилища потока
GSРегистр дополнительного сегментаОбщего назначения, обычно используется для локального хранилища потока

При программировании в 32-битном защищённом режиме операционная система уже настроила сегментные регистры, поэтому обычно не нужно изменять их вручную.


Регистры указателей и индексов

Эти регистры в основном используются для доступа к памяти и хранения адресов памяти:

РегистрПолное названиеНазначение
EIPУказатель командУказывает на адрес следующей команды, которая должна быть выполнена ЦП (недоступен напрямую)
ESPУказатель стекаУказывает на вершину стека; инструкции PUSH/POP автоматически изменяют его
EBPУказатель базыУказывает на основание стекового кадра текущей функции; используется для доступа к параметрам функции и локальным переменным
ESIИндекс источникаАдрес источника для строковых операций и операций с памятью
EDIИндекс назначенияАдрес назначения для строковых операций и операций с памятью

ESP и EBP нельзя свободно использовать как обычные регистры общего назначения. ESP указывает на вершину стека, а push/pop/call/ret изменяют его; EBP имеет ключевое значение для доступа к параметрам функции. Произвольное изменение их приведёт к аварийному завершению программы.


Регистр флагов (EFLAGS)

EFLAGSЭто 32-битный регистр, каждый бит которого представляет флаг состояния.

Нельзя напрямую читать или записывать весь EFLAGS, но CPU автоматически обновляет эти биты флагов на основе результатов операций, а инструкции условного перехода используют их, чтобы решить, следует ли выполнять переход.

Бит флагаNameЗначение
CFФлаг переносаУстанавливается в 1, когда при беззнаковой арифметике возникает перенос/заём
PFФлаг чётностиУстанавливается в 1, когда количество единиц в младших 8 битах результата чётно
AFФлаг вспомогательного переносаУстанавливается в 1, когда возникает перенос/заём из младших 4 бит в старшие 4 бита
ZFФлаг нуляУстанавливается в 1, когда результат операции равен 0
SFФлаг знакаУстанавливается в 1, когда результат операции отрицательный (равен старшему значащему биту результата)
OFФлаг переполненияУстанавливается в 1 при переполнении знаковой арифметики

Пример

; Путь к файлу: flags_demo.asm
; Демонстрирует влияние арифметических операций на биты флагов

section .text
    global _start

_start:
    mov eax, 10
    sub eax, 10         ; 10 - 10 = 0
    ; ZF = 1 (результат равен нулю)
    ; SF = 0 (результат неотрицательный)
    ; CF = 0 (нет заёма)

    mov eax, 0xFFFFFFFF
    add eax, 1          ; 0xFFFFFFFF + 1 = 0x100000000 (превышает 32 бита)
    ; ZF = 1 (32-битный результат равен 0)
    ; CF = 1 (возник перенос)
    ; OF = 0 (нет переполнения с точки зрения знаковых чисел)

    mov eax, 1
    mov ebx, 0
    int 0x80

Соглашения об использовании регистров

В реальном программировании некоторые регистры имеют традиционное применение — это называетсясоглашением о вызовах:

РегистрНазначение в соглашении о вызовах
EAXСохранение возвращаемого значения функции
ECXСчётчик (количество итераций цикла)
EDXСохранение старшей части результатов деления, расширение EAX
EBX, ESI, EDI, EBPСохраняемые вызываемой функцией: вызываемая функция должна сохранять и восстанавливать их
EAX, ECX, EDXСохраняемые вызывающей стороной: вызывающая сторона отвечает за сохранение

При написании собственных ассемблерных программ не обязательно строго следовать соглашению о вызовах. Но если вы смешиваете код с C, необходимо следовать соглашению о вызовах cdecl.

Другие расширения