Эпиграф

“The registers are the CPU”s own workspace. They are not memory, and they are not variables. They are the places where the processor keeps the data it is working on right now — like a craftsman”s bench, where only the tools and materials for the current task are kept.”

“The stack is not just a pile of return addresses. It is the memory of the program”s decisions. Every call is a promise to return; every push is a thought set aside for later. In real mode, the stack pointer (SP) is the program”s short‑term memory.”

“Segments are not an arbitrary limitation. They are a reflection of the physical world — of address lines, of memory chips, of the way data flows from one place to another. The segment:offset model is not a hack; it is a bridge between 16-bit registers and 20-bit addresses.”

— Рэй Дункан (Ray Duncan): »Programming in the MS‑DOS Environment», »Advanced MS‑DOS Programming»

"Registers are the fastest storage locations in the computer. But their speed is not what makes them special. What makes them special is that they are the only locations the CPU can operate on directly. You don”t compute with memory — you compute with registers, and memory is just a place to keep data between uses.”

“The stack is the foundation of structured programming. Without it, there would be no functions, no recursion, no local variables. The call and ret instructions are the verbs of program flow, and the stack is their grammar.”

“Segmentation is often misunderstood as a flaw, but in the context of the 8086, it was a brilliant solution. It allowed a 16-bit processor to address 1 MB of memory — a compromise between cost, complexity, and capability. The segment registers (CS, DS, SS, ES) are not overhead — they are the keys to the kingdom.”

— Рэндэлл Хайд (Randall Hyde): »The Art of Assembly Language»

“Charm can fool you.”

— Ральф Броун (Ralf Brown): «Ralf Brown»s Interrupt» List» (часть 1, часть 2, часть 3)

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

Цитаты Рэя Дункана, Рэндэлла Хайды и Ральфа Броуна — это не просто отрывки из книг. Это фрагменты интеллектуального наследия, на котором выросло поколение программистов, реверс‑инженеров и создателей игр.

  • Рэй Дункан в своих книгах, «Programming in the MS‑DOS Environment» и «Advanced MS‑DOS Programming», не просто объяснял API DOS. Он учил мыслить в рамках реального режима, понимать, как железо, память и прерывания работают вместе. Его работы были библией для тех, кто писал на C и ассемблере под DOS. Они до сих пор остаются одними из самых точных описаний архитектуры 16-битной системы;

  • Рэндэлл Хайд с «The Art of Assembly Language» сделал невозможное — он превратил ассемблер из «языка машин» в предмет изучения, поддающийся логике и структуре. Его книга — это не справочник, а философия программирования, где каждая инструкция рассматривается как элемент мышления. Для многих, и меня в том числе, она стала первым шагом к пониманию, как работает компьютер «изнутри»;

  • Ральф Броун и его «Interrupt List» — это архив знаний, собранный вручную, без доступа к закрытым спецификациям. Его список прерываний — не просто справочник, а результат кропотливой работы, где каждый байт, каждая функция, каждый селектор были найдены, проверены и систематизированы. RBIL использовался при создании DOSBox, QEMU, FreeDOS и стал основой для всех, кто пытается понять, как работали старые программы

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

Регистры

На Хабре, да и в других технических сообществах, я не раз видел дискуссии о том, что такое регистры. Один говорит: «Регистры — это переменные в CPU». Другой: «Это ячейки памяти». Третий: «Это состояния, а не данные». И все они, в какой‑то мере, правы. Но все они — часть истины. Я не люблю говорить: «Читайте документацию». Когда меня спрашивают, стараюсь объяснить понятно, по‑человечески, без сухих цитат. Но с регистрами решил поступить иначе. Не хочу навязывать своё определение, не хочу устраивать холивар, не хочу чтобы через год кто‑то написал: «Автор ошибся! Регистр — это не то, что он сказал». Я дам вам то, на что опираются все определения — ссылки на официальные документы Intel, от самого первого 4004 до современных x86–64. Это не побег от ответа, а приглашение встать на твёрдую почву. То, что я не могу объяснить без искажений, вы сможете прочитать в том, где это было впервые записано.

Процессор

Год

Документ

Ссылка

Intel 4004

1971

MCS-4 User’s Manual

archive.org

Intel 8008

1972

8008 User's Manual

archive.org

Intel 8080

1974

8080 User's Manual

archive.org

Intel 8086

1978

8086 Family Manual

archive.org

Intel 80286

1982

80286/80287 PRM

archive.org

Intel 80386

1985

80386 PPR

archive.org

Intel 80486

1989

80486 PRM

archive.org

IA-32 / x86-64

наше время

Intel SDM

intel.com

Вывод: Регистры — это программно доступные ячейки хранения, определённые архитектурой, предназначенные для временного хранения операндов, адресов, состояния выполнения и управляющих данных. Они являются основным интерфейсом между программой и процессором, используются для выполнения арифметических, логических, адресных и управляющих операций.

— На основе: Intel® 386™ Programmer»s Reference Manual (1986), Volume 1, Section 3.2 «Registers», и Intel 80 486™ Microprocessor Family Programmer»s Reference Manual (1992), Chapter 3 «Architecture Overview»

Примечание: Некоторые разделы на сайте компании Intel могут быть недоступны для пользователей из России и Белоруссии. Я знаю что такое недоразумение не остановит ИТ‑умников и ИТ‑умниц, они смогут найти всю интересующую их информацию.

Категории регистров в 80386/80486

Регистры общего назначения (General-Purpose Registers, GPR).

Размер: 32 бита (в защищённом режиме) и 16 бит (в реальном режиме).
Назначение: Выполнение арифметических, логических операций, хранение адресов, параметров и временных данных.

32 бита

16 бит

8-битные части

Короткое описание

EAX

AX

AH, AL

Аккумулятор (Accumulator)

EBX

BX

BH, BL

Базовый регистр (Base Register)

ECX

CX

CH, CL

Счётчик (Count Register)

EDX

DX

DH, DL

Регистр данных (Data Register)

ESI

SI

-

Индекс источника (Source Index)

EDI

DI

-

Индекс назначения (Destination Index)

ESP

SP

-

Указатель стека (Stack Pointer)

EBP

BP

-

Базовый указатель (Base Pointer)

Подробное описание каждого регистра

EAX / AX / AH / AL — Аккумулятор (Accumulator).

Примечание: Термин «аккумулятор» уходит своими корнями в самые ранние процессоры 8080 и 8086

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

Особенности:

  • Используется по умолчанию в операциях mul, div, in, out;

  • Возвращает результат функций (аналог return в C);

  • Часто используется в прерываниях DOS/BIOS (например, int 21h).

Примеры:

/* Turbo C 2.1 */
#include <dos.h>

void main(void)
{
  _AX = 0x13;         // Это то же самое, что и `mov ax, 13h`
  geninterrupt(0x10); // Это то же самое, что и `int 10h`
}
; байты: b8 13 00
mov ax, 13h    ; установка видеорежима BIOS в VGA 300x200, 256 цветов
               ; код mov ax, 13h равноценен такому коду из двух строк:
               ;
               ; mov ah, 00h
               ; mov al, 13h
; байты: cd 10
int 10h
/* Turbo C 2.1 */
#include <dos.h>
#include <stdio.h>

void main(void)
{
  int res;
  res = 5;
  res = res + 10;
}
; байты: be 05 00
mov si, 0x5    ; SI = 5
; байты: 8b c6
mov ax, si     ; переносим значение из источника в аккумулятор AX = SI = 5
; байты: 05 0a 00
add ax, 0xa    ; AX = 15, SI = 5

EBX / BX / BH / BL — Базовый регистр (Base Register)

Примечание: Почему «базовый»? EBX/BX часто служит основой для вычисления адреса

Назначение: хранение адреса, от которого начинается доступ к данным в памяти.

Особенности:

  • Используется в режимах адресации вида [ebx + esi], [bx + di] и т.д.;

  • Часто указывает на начало структуры данных, таблицы или массива.

Пример:

mov ebx, [0x1234]     ; адрес в памяти, в котором, предположительно,
                      ; находится структура player
mov eax, [ebx + 4]    ; получить координату Y

ECX / CX / CH / CL — Счётчик (Count Register)

Назначение: хранение счётчика итераций.

Особенности:

  • Часто используется в инструкциях loop, loope, loopne;

  • Используется в строковых операциях с префиксом rep (rep movsb, rep stosb);

  • В инструкциях сдвига (shl, shr) CL указывает количество бит для сдвига.

Пример:

mov cx, 0x64       ; 100 итераций
dec ax
loop 0x2345        ; адрес цикла

EDX / DX / DH / DL - Регистр данных (Data Register)

Назначение: часто используются в определенных инструкциях, таких как: ввод-вывод, умножение, деление.

Особенности:

  • В паре с EAX образует 32-битное значение (в 16-битном режиме) для mul и div;

  • Используется для передачи параметров в DOS API (например, DS:DX - адрес строки).

Примеры:

mov ax, 50000
mov dx, 0
div bx           ; AX = частное, DX = остаток
mov dx, [0x3456] ; по этому адресу хранится имя файла
mov ah, 3Dh      ; функция открытия файла
int 21h          ; открыть файл

ESI / SI — Индекс источника (Source Index)

Назначение: указывает на источник данных в операциях со строками и массивами.

Особенности:

  • Используется с префиксом rep в movsb, movsw, movsd ;

  • Автоматически инкрементируется или декрементируется в зависимости от флага DF .

Пример:

mov esi, [0x1234]
mov edi, [0x5678]
mov ecx, 100
rep movsb          ; скопировать 100 байт

EDI / DI — Индекс назначения (Destination Index)

Назначение: указывает на источник данных в операциях со строками и массивами.

Особенности:

  • Используется в movsb, stosb, cmpsd и других инструкциях;

  • Часто указывает на видеопамять (0xA000h) или буфер вывода;

  • В паре с ESI используется для копирования и сравнения блоков памяти.

Пример:

mov edi, 0xA000h    ; видеопамять
mov al, 15          ; белый пиксель
mov ecx, 64000      ; 320x200
rep stosb           ; заполнить экран

ESP / SP — Указатель стека (Stack Pointer)

Назначение: указывает на вершину стека.

Особенности:

  • Автоматически изменяется командами push, pop, call, ret ;

  • Используется в паре с SS (сегмент стека): SS:ESP.

Пример:

push ax         ; ESP -= 2
pop bx          ; BX = [ESP], ESP += 2

Примечание: Не рекомендуется изменять напрямую

EBP / BP — Базовый указатель (Base Pointer)

Назначение: указывает на начало кадра стека текущей функции.
Особенности:

  • Используется для доступа к параметрам ([ebp+8]) и локальным переменным ( [ebp-4] );

  • Сохраняется в начале функции: push bp ; mov bp, sp ;

  • Позволяет строить стек-трейсы.

Пример:

push bp
mov bp, sp
mov ax, [bp+4]   ; первый параметр
mov bx, [bp-2]   ; локальная переменная
pop bp
ret

Примечание: EBP (BP) — ключевой регистр для реверс-инжиниринга, так как позволяет восстановить сигнатуры функций.

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

Сегментные регистры (Segment Registers)

Размер: 16 бит.
Назначение: Хранение селекторов сегментов, используемых для адресов в памяти.

Регистр

Описание

Назначение

CS

Code Segment

Сегмент кода — откуда процессор читает инструкции (IP — указатель внутри CS).

DS

Data Segment

Сегмент данных — используется по умолчанию при доступе к данным.

SS

Stack Segment

Сегмент стека — используется с SP для доступа к стеку.

ES

Extra Segment

Экстра сегмент — часто используется в операциях со строками (movsb, stosb).

FS, GS

80 386+

Frame Segment и Global Segment — дополнительные сегменты, используются в 64-битной архитектуре

Зачем они нужны в реверсе?

При анализе.COM или.EXE файлов под DOS сегментные регистры критически важны:

  • Программа одновременно может работать с несколькими сегментами;

  • Смещения могут быть одинаковыми, но указывать на разные области памяти;

  • Непонимание сегментов ведёт к неверному толкованию данных и кода.

Что нужно знать:

  • CS:IP — всегда текущая инструкция. CS — сегмент кода, IP — указатель команд. При запуске программы DOS загружает её в сегмент, значение которого неизвестно на этапе анализа. Ghidra и IDA по умолчанию используют относительные адреса (например, 0×100), но в реальности это CS:0x100, где CS — динамическое значение. Если вы видите call 0×150, это не абсолютный адрес. Это относительный вызов внутри сегмента. Физический адрес: (CS << 4) + 0×150. Процессор автоматически обновляет EIP (IP) после каждой инструкции;

  • DS — сегмент данных: где хранятся строки, массивы, структуры. В DOS‑программах DS часто инициализируется как CS (особенно в.COM файлах), потому что код и данные находятся в одном сегменте. Если вы видите mov ax, cs; mov ds, ax — значит, данные и код в одном сегменте;

mov ax, cs
mov ds, ax
  • DS:DX — передача строк в DOS API и оканчивающаяся символом $.DX — смещение, DS — сегмент. Если DS не установлен, строка не будет найдена и возможно, игра использует односегментную модель (код = данные);

mov ah, 09h           ; функция: вывод строки
mov dx, [0x1234]      ; смещение строки
mov ax, cs
mov ds, ax            ; DS = CS
int 21h
  • SS:SP — стек, где хранятся параметры и возвраты. SS — сегмент стека, SP — указатель стека. В DOS-программах SS и SP часто инициализируются автоматически, но иногда устанавливаются вручную.

mov ax, stack_seg
mov ss, ax
mov sp, 0xFFFE
  • ES — дополнительный сегмент: часто используется с DI. В операциях со строками ES используется как сегмент назначения. Например, movsb копирует байт из DS:SI в ES:DI .

mov ax, 0xA000
mov es, ax
mov di, 0               ; начало видеопамяти
mov al, 15              ; белый пиксель
mov cx, 64000           ; 320x200
rep stosb               ; заполнить экран

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

EFLAGS (FLAGS) — это 32-битный (16-битный) регистр состояния процессора, который хранит результаты выполнения инструкций и управляет поведением программы. Он не используется для хранения данных, но определяет, как будут работать условные переходы, циклы и отладка.

— На основе: Intel® 386™ Programmer’s Reference Manual, Volume 1, Section 3.2.3 "EFLAGS Register"

В контексте дизассемблирования, EFLAGS (FLAGS) — это «невидимый» аргумент условных операций. Вы не видите его напрямую в mov или add , но он полностью контролирует, куда пойдёт выполнение после cmp, test, sub.

Бит

Сокращение

Название

16 бит

32 бита

Описание

0

CF

Carry Flag

+

+

Флаг переноса — устанавливается при переполнении арифметической операции

1

Reserved

2

PF

Parity Flag

+

+

Чётность — устанавливается, если количество единиц в младших 8 битах чётное

3

Reserved

4

AF

Auxiliary Carry Flag

+

+

Вспомогательный флаг переноса для BCD (Binary Coded Decimal)

5

Reserved

6

ZF

Zero Flag

+

+

Флаг нуля — устанавливается, если результат равен нулю

7

SF

Sign Flag

+

+

Флаг знака результата — устанавливается, если результат отрицательный

8

TF

Trap Flag

+

+

Флаг трассировки — устанавливается в «пошаговый» режим (для отладки)

9

IF

Interrupt Flag

+

+

Флаг прерываний — для управления прерываниями

10

DF

Direction Flag

+

+

Флаг направления — для направления операций со строками

11

OF

Overflow Flag

+

+

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

12

IOPL

I/O Privilege Level

+

Уровень привилегий ввода‑вывода — уровень привилегий текущего выполняемого потока. IOPL 0 — это режим ядра, а 3 — пользовательский режим.

13

IOPL

I/O Privilege Level

+

14

NT

Nested Task Flag

+

Флаг вложенной задачи — управляет цепочкой прерываний

15

Reserved

16

RF

Resume Flag

+

Флаг возобновления — используется для обработки исключений во время отладки

17

VM

Virtual 8086 Mode Flag

+

Флаг режима виртуальной машины 8086 — устанавливается для режима совместимости с 8086

18

AC

Alignment Check Flag

+

Флаг проверки выравнивания — устананавливается для проверки выравнивания памяти

19

VIF

Virtual Interrupt Flag

+

Флаг виртуального прерывания — виртуальная версия флага IF в виртуальном режиме 8086

20

VIP

Virtual Interrupt Pending Flag

+

Флаг ожидания виртуального прерывания — устанавливается, когда прерывание находится в состоянии ожидания в виртуальном режиме 8086

21

ID

Identification Flag

+

Флаг ID — если этот флаг установлен, то поддерживается инструкция cpuid

Основные флаги в реверсе:

CF (Carry Flag) (бит 0) — перенос и заём

  • Устанавливается при переносе из старшего бита (add, adc) или заеме (sub, sbb);

  • Используется в jc (jump if carry), jnc, а также в операциях с двойной точностью

Пример:

add ax, bx     ; если переполнение, то CF = 1
jc overflow    ; перейти, если переполнение

ZF (Zero Flag) (бит 6) — основа условных переходов:

  • Устанавливается, если результат операции равен нулю;

  • Используется в jz (jump if zero), jnz (jump if not zero).

Пример:

cmp ax, 10     ; условие: если AX == 10, то ZF = 1
jz label_done  ; если ZF = 1, то перейти по метке

SF (Sign Flag) (бит 7) и OF (Overflow Flag) (бит 11) — проверка знака и переполнения:

  • SF = 1, если результат отрицательный (старший бит = 1);

  • OF = 1, если произошло переполнение со знаком (например, 7FFFh + 1 → 8000h)

Пример:

cmp eax, ebx
jl less_than   ; jl (less), jg (greater) — не проверяют ZF, а используют SF и OF.

TF (Trap Flag) (бит 8) — «пошаговый» режим:

  • TF = 1 — после каждой инструкции вызывается исключение (Single Step);

  • Используется отладчиками (например: debug, SoftICE и др.)

Пример (управляемый код):

pushf
pop ax
or ax, 0100h     ; установить TF
push ax
popf             ; TF = 1

Анти-реверс (один из множества примеров):

pushf
pop ax
test ax, 0100h
jnz 0x1234       ; будьте уверены, вас здесь послали... на другую логическую ветку
                 ; на анализ которой, вы потратите драгоценное время
                 ; и это в лучшем случае...

IF (Interrupt Flag) (бит 9) — управление прерываниями:

  • IF = 1 — маскируемые прерывания (IRQ) разрешены;

  • IF = 0 — запрещены (критическая секция);

  • sti — set interrupt flag ( IF = 1);

  • cli — clear interrupt flag ( IF = 0). Часто используется: перед изменением векторов прерываний, в критических секциях, в драйверах.

DF (Direction Flag) (бит 10) — направление строковых операций:

  • DF = 0 — строковые операции (movsb, lods, stos) идут вперёд (инкремент SI/DI);

  • DF = 1 — назад (декремент)

  • cld — clear direction flag ( DF = 0)

  • std — set direction flag ( DF = 1)

Пример:

cld               ; всегда надо проверять наличие. Если cld отсутствует перед rep,
                  ; то возможно DF = 1 и копирование пойдёт назад
mov si, [0x1234]  ; источник
mov di, [0x5678]  ; назначение
mov cx, 100
rep movsb         ; копировать 100 байт вперёд

Важно помнить:

  • Флаги устанавливаются не только cmp . Многие инструкции неявно изменяют EFLAGS : add, sub, and, or, xor, test, dec, inc, shl, shr и др. test ax, ax - частый паттерн для проверки, равен ли регистр нулю

test ax, ax     ; установит ZF, если AX == 0
jz zero_value
  • inc и dec не изменяют CF. inc eax — увеличивает EAX, не затрагивая CF. add eax, 1 — увеличивает EAX, устанавливает CF при переполнении. Это используется в оптимизации и анти-реверсе

inc ax
jc overflow   ; никогда не сработает!
  • Некоторые инструкции используют комбинацию флагов: jo / jno — Overflow, js / jns — Sign, jp / jnp — Parity, ja / jb — Above/Below (на основе CF и ZF)

cmp al, 0Ah
jb is_digit    ; если AL < 10 (unsigned)

Понимание EFLAGS (FLAGS) — ключ к расшифровке условных переходов, циклов, сравнений и защиты от отладки

Прерывания DOS: тайна, скрытая в коде

Представьте: вы запускаете DOS‑игру, то на экране отображаются ваши любимые герои, игра реагирует на ваши нажатия, записывает сохранки на диск... Но как это вообще возможно? Как эта самая программа может «общаться» с компьютером, не зная ни одного из его внутренних механизмов? Ответ кроется в прерываниях, «тайном языке», который знает каждый компьютер, но из людей мало кто его понимает. Прерывания — это как секретный код, скрытый в самом сердце DOS. Когда программа вызывает прерывание 10h, она «говорит»: «Пожалуйста, выведи текст на экран». Когда вызывается 16h — «Считай нажатие клавиши». Когда 21h — «Открой файл и прочитай из него данные».

Именно эти прерывания создают то самое волшебство, которое делало DOS-программы такими мощными, а игры такими увлекательными. Они позволяли программам использовать возможности компьютера, как будто они были частью системы.

Именно теперь вы готовы открыть дверь, за которой скрывается истина о том, как работают DOS-программы. Впереди вас ждут не просто цифры и коды, а настоящая археология программного обеспечения. Мы не будем изучать весь RBIL, но познакомимся с несколькими важными прерываниями, которые вы всегда встретите в DOS-играх.

Почему прерывания отправная точка для реверса DOS-игр?

Прерывания - это маяки и они:

  • Используют одну и ту же инструкцию в формате - int N;

  • Выполняют конкретные, понятные действия: вывод на экран, чтение клавиш, открытие файлов и т. д.;

  • Находятся в начале и/или в конце функций, в циклах;

  • Перед вызовом int программа подготавливает регистры, чтобы передать параметры.

int 21h — Самое часто используемое прерывание в API DOS: файлы, ввод, выход и т .д.

Код функции

AH

Описание функции

09h

AH = 09h

Вывести строку с терминатором ($):
"I'll be back$"

3Dh

AH = 3Dh

Открыть файл

3Fh

AH = 3Fh

Прочитать из файла

40h

AH = 40h

Записать в файл

4Ch

AH = 4Ch

Завершить программу

Пример (загрузка файла):

mov ah, 3Dh           ; функция: открыть файл
mov al, 0             ; доп. аргумент для функции: режим - только чтение
mov dx, [0x1234]      ; DS:DX - строка с именем файла
int 21h               ; инициализация прерывания
mov bx, ax            ; сохраним дескриптор файла

Пример (выход из приложения):

mov ah, 4Ch        ; функция: завершить программу
mov al, 0          ; доп. аргумент для функции: код возврата — 0
int 21h

int 10h — BIOS: видео и графика

Код функции

AH

Описание функции

00h

AH = 00h

Установить видеорежим (список режимов)

02h

AH = 02h

Установить курсор

0Ch

AH = 0Ch

Нарисовать пиксель

Пример (инициализация графического режима):

mov ax, 13h    ; ah = 00h - функция: установить видеорежим
               ; доп. аргумент для функции:
               ; al = 13h - 320x200, 256/256K, VGA,MCGA,ATI VIP, text: 40x25
int 10h        ; вызов BIOS

Пример (отрисовка пикселя):

mov ah, 0Ch    ; функция: нарисовать пиксель
mov al, 15     ; доп. аргумент для функции: цвет - белый
mov cx, 100    ; X
mov dx, 50     ; Y
int 10h

int 16h - BIOS: ввод с клавиатуры

Код функции

AH

Описание функции

00h

AH = 00h

Ждать нажатия (блокирующий)

01h

AH = 01h

Проверить, нажата ли клавиша (неблокирующий)

Пример (ожидание ввода с «зависанием» игры (блокировкой)):

mov ah, 00h    ; функция: ждать нажатия
int 16h        ; результат: AL = ASCII, AH = скан-код

int 33h — мышь (если поддерживается)

Код функции

AH

AL

Описание функции

00h

AH = 00h

AL = 00h

Инициализировать мышь (в начале игры)

00h

AH = 00h

AL = 03h

Получить позицию мыши (в цикле)

Пример:

mov ax, 0000h   ; функция: инициализировать мышь
int 33h         ; если AX != 0000h — мышь есть

Таймеры:

int 08h — таймер: используется анимаций, физики.

int 1Ch — пользовательский таймер. Можно установить свой обработчик для фоновых задач

Важно знать: int 20h — «старый» способ выхода (Terminate), используется в .COM файлах. Не использует код возврата

Работаем с Ghidra:

Вы можете помечать функции на основе тех прерываний, которые в них указаны (примеры):

  • FUN_00401000 → init_video() (если в ней вызывается int 10h);

  • FUN_00401200 → read_key() (если в ней вызывается int 16h);

  • FUN_00401500 → load_file() (int 21h, AH=3Dh)

Мы начали с цитат тех людей, котрые внесли максимаьный вклад в программирование под DOS. С Рэя Доббса, чьи книги «Programming in the MS‑DOS Environment» и «Advanced MS‑DOS Programming» научили нас мыслить в рамках реального режима, понимать, как код, данные и прерывания работают вместе. С Рэндэлла Хайды, чья «The Art of Assembly Language» превратила ассемблер из «языка машин» в предмет мышления, где каждая инструкция часть системы. И с Ральфа Броуна, чей Interrupt List стал для нас картой мира, в котором каждое int 10h или int 21h — это сигнал.

Именно их труды позволили нам перестать бояться .COM и.EXE‑файлов. Мы начать читать то, что казалось непознаваемым: регистры, стек, сегменты, флаги, прерывания. Мы разобрали, как AX используется для передачи номера функции в int 10h, как CX служит счётчиком в циклах, как DS:DX указывает на строку в int 21h, как EFLAGS управляют переходами после cmp. Мы увидели, как регистры и сегменты работают вместе, и как прерывания становятся точками соприкосновения с системой. Мы увидели примеры кода на ассемблере и C, чтобы показать, как сильно они связаны.

Но это только продолжение того сложного пути, начатого в первой части. Теперь мы подошли к следующему рубежу — воссозданию кода. Впереди: разбор операндов, режимов адресации, структур данных. Попытаемся взять настоящие функции из DOS‑игры, «расшифровать» их логику и впервые написать на C код, который делает то же самое. Потому что реверс‑инжиниринг — это не просто анализ, а движение, от байтов к смыслообразующему коду.

P.S.: Все исходники и анализ кода, для этой статьи, будут опубликованы на GitHub через некоторое время. На данный момент готовы исходники только для Turbo C 2.1 и Turbo C++ 3.1. Частично готовы для TASM 4. Дополнительно готовятся на Watcom C 8.0 и Watcom С 10.5. Каждый компилятор по своему работает с оптимизацией кода, отличается работа с регистрами

Комментарии (6)


  1. JordanCpp
    14.08.2025 14:28

    Спасибо за статью. Буду перечитывать.


    1. NeriaLab Автор
      14.08.2025 14:28

      Самое интересное будет в третьей части - практика. Первая часть подготовка инструментов и предварительный анализ, вторая часть - работа с информацией: RBIL, Art, мануалы. К третьей части нельзя подходить без первых двух.


  1. NightBlade74
    14.08.2025 14:28

    Frame Segment и Global Segment - дополнительные сегменты, используются в 64-битной архитектуре

    Ну, тут какая-то ерунда написана, i80386 появился за 18 лет до появления x64 (у Intel), так что два дополнительных сегментных регистра решали проблему дефицита сегментных регистров общего назначения (CS и SS, все же, специализированные). Особенно этот дефицит был заметен в защищенном режиме, т.к. влиял на производительность, поскольку тут в регистры записывался не адрес сегмента, а дескриптор, загрузка которого требовала еще множества телодвижений с таблицей дескрипторов и страниц виртуальной памяти.

    Регистр SP я бы не относил к регистрам общего назначения, все ж таки он специальный. У самого Intel в документации регистры разделены на Data Group и Pointer and Index Group. Неплохо было бы указать, что стек растет со старших адресов к младшим, это важно.

    IP вообще отдельным разделом обделен, что совершенно несправедливо, хоть он и не доступен программисту напрямую.

    Может быть я пропустил, но не вижу, чтобы где-то указывалось, с какими сегментами по умолчанию работают разные указательные/индексные регистры.


    1. drWhy
      14.08.2025 14:28

      IP ... не доступен программисту напрямую.
      ЕМНИП разрешено, хотя могу путать
      POP IP


      1. NeriaLab Автор
        14.08.2025 14:28

        "The Art of Assembly Language" - страницы 290 и 291


    1. NeriaLab Автор
      14.08.2025 14:28

      1. Да, FS и GS появились раньше, но они не используются в 32 битах так, как они используются в 64 битах, там куча ограничений и именно из-за них, не стал зацикливать на них внимание. Я много DOS игр "развернул" и с ними не сталкивался, хотя о них знаю;

      2. ESP (SP) вот сами прочтите здесь (386™ SX MICROPROCESSOR PROGRAMMER'S REFERENCE MANUAL, "2.3.1 General Registers", p.2-7). Как я писал ранее - не хочу и не буду устраивать холивар;

      3. Спасибо за Вашу критику и внимание, но тема несколько иная - здесь про реверс, а не изучение тонкостей ЯП Assembler. Именно для изучения Assembler я и дал ссылку на "библию" - "The Art of Assembly Language".

        Зачем я даю ссылки на официальную документацию, на книги? Для того, чтобы люди сами всё прочитали, узнали что-то для себя. Если бы я в первой части учил бы работать с теми инструментами, которые я описал, то только бы через год, а может и два, приступил ко второй части. И еще через пару-тройку лет, к третьей.

      P.S.: Небольшое интервью Хайда - "The rebirth of assembly language programming" (October 13, 2003)