Погружение в ассемблер. Осваиваем арифметические инструкции — «Хакер»

Найти:

- Взлом
- Приватность
- Трюки
- Кодинг
- Админ
- Geek
- Обучение
- Магазин

- Windows
- Linux
- Android
- Железо
- Мессенджеры
- Утечки
- Коронавирус
- Все выпуски «Хакера»

### Содержание статьи

- Другие статьи курса
- Пишем вспомогательную подпрограмму
- Учимся складывать и вычитать
- Осваиваем инструкцию умножения
- Разбираемся с инструкцией деления
- Логический и арифметический сдвиги, циклический сдвиг
- Три логические инструкции плюс одна бесполезная
- Знакомимся с инструкциями инкремента и декремента
- Пишем простую игрушку «Угадай число»
- Выводы
- Инструкции и операторы

Прочитав эту статью, ты научишься пользоваться арифметическими и логическими инструкциями, а также инструкциями сдвига. Попутно узнаешь, как создавать подпрограммы. А в конце напишешь простенькую игрушку «Угадай число».

### Другие статьи курса

- Зачем учить ассемблер в 2020 году
- Делаем первые шаги в освоении асма
- Как работают переменные, режимы адресации, инструкции условного перехода
- Учимся работать с памятью
- Работаем с большими числами и делаем сложные математические вычисления
- Сокращаем размер программы
- Пишем клон игры Flappy Bird, который уместится в бутсектор
- Пишем бейсик и умещаем его в 512 байт

Большинство тех инструкций, о которых ты узнаешь из этой статьи, принимают на входе два аргумента. Каждый отдельно взятый аргумент — это либо регистр, либо память, либо константа. Почти все их сочетания допустимы: регистр-регистр, регистр-память, память-регистр, регистр-константа, память-константа. Единственное недопустимое сочетание — память-память.

Регистры могут быть 16- или 8-битными. 16-битные регистры: `AX`, `BX`, `CX`, `DX`, `SI`, `DI`, `BP` и `SP`. 8-битные регистры: `AH`, `AL`, `BH`, `BL`, `CH`, `CL`, `DH` и `DL`.

## Пишем вспомогательную подпрограмму

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

Что такое подпрограмма? Это кусок кода, который выполняет какую-то небольшую задачу. К подпрограмме обычно обращаются через инструкцию `call`. Все подпрограммы заканчиваются инструкцией `ret` (RETurn).

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

Реализация подпрограммы `call display_letter` представлена ниже. Сохрани ее в файл `library.asm`.

Важно! В конец всех программ, которые мы напишем, тебе надо будет вставлять код из `library.asm`. К чему это приведет? Все программы будут заканчиваться выходом в командную строку, и у них будут подпрограммы для вывода символа на экран (из регистра `AL`) и для считывания символа с клавиатуры (результат помещается в регистр `AL`).

## Учимся складывать и вычитать

В качестве аргументов для инструкции сложения давай задействуем регистр `AL` и константу.

Эта программа выводит на экран цифру `7`. Потому что `4 + 3 = 7`.

В качестве аргументов для инструкции вычитания давай возьмем регистр `AL` и константу.

Эта программа выводит на экран цифру `1`. Потому что `4 – 3 = 1`.

## Осваиваем инструкцию умножения

Инструкция умножения умеет работать с байтами (8-битные числа) и словами (16-битные числа). Умножаемое — это всегда регистр `AL`/`AX`. А множителем может быть либо регистр (любой), либо переменная в памяти.

Только учти, что если умножаемое у тебя в `AL`, то множитель должен быть 8-битным, а если в `AX` — 16-битным. Результат умножения попадает либо в `AX` (когда перемножаем два 8-битных числа), либо в `DX:AX` (когда перемножаем два 16-битных числа).

В примере ниже мы используем два 8-битных регистра `AL` (умножаемое) и `CL` (множитель). Результат попадает в 16-битный регистр `AX`.

Эта программа выводит на экран цифру `6`. Потому что `3 × 2 = 6`.

Для того чтобы перемножить два 16-битных числа, умножаемое помести в `AX`, а множитель — в `CX`. Затем вместо `mul cl` напиши `mul cx`.

Обрати внимание, инструкция `mul` оперирует беззнаковыми целыми. Если тебе надо перемножить числа со знаком, используй `imul`.

## Разбираемся с инструкцией деления

Инструкция деления умеет работать со словами (16-битные числа) и двойными словами (32-битные числа). Делимое — это всегда либо регистр `AX`, либо `DX:AX`. А делителем может быть либо регистр (любой), либо переменная в памяти.

Только учти, что если делимое у тебя в `AX`, то делитель должен быть 8-битным, а если в `DX:AX` — 16-битным.

Когда ты делишь 16-битное число на 8-битное, то результат попадает в `AL`, а остаток — в `AH`. Если делишь 32-битное число на 16-битное, результат попадает в `AX`, остаток — в `DX`.

В примере ниже мы используем 16-битный и 8-битный регистры. Результат попадает в `AL`, остаток в `AH`.

Эта программа выводит на экран цифру `3`. Потому что `100 / 33 = 3`. Если хочешь посмотреть, какой остаток получился, добавь вот такую строчку сразу после той, где написана инструкция `div`.

Берегись! Инструкция деления может сломать твою программу. Если ты сделаешь деление на ноль, то возникнет системная ошибка и твоя программа вылетит в командную строку.

Обрати внимание, инструкция `div` оперирует беззнаковыми целыми. Если тебе надо разделить числа со знаком, используй `idiv`.

## Логический и арифметический сдвиги, циклический сдвиг

Инструкции сдвига (операторы `<<` и `>>` в языках высокого уровня) — это самые родные инструкции для процессора. Работают они быстрее, чем большинство других инструкций. Так что если какую-то часть вычислений можно реализовать на них, — особенно если это позволит избежать инструкций умножения и деления, — смело их используй.

Сейчас объясню, как здесь работает инструкция сдвига. Представь, что значение регистра `AL` — это число в двоичной системе счисления. Инструкция `shl` просто сдвигает каждый бит двоичного числа на одну позицию влево и добавляет справа ноль. А тот бит, который вытесняется слева, попадает в флаг `CF` (Carry Flag; флаг переноса).

Аналогичным образом работает инструкция `shr`, но только она сдвигает биты регистра вправо, а не влево.

Есть еще инструкция `sar`, которая работает почти как `shr`, но в отличие от `shr` делает не логический, а арифметический сдвиг. Что это значит? Когда `sar` сдвигает бит двоичного числа вправо, то не добавляет ноль, а дублирует бит, который там был до сдвига. Иногда это может быть и ноль, но не всегда.

В чем польза от такой хитроумной альтернативы обычному сдвигу вправо? В том, что `sar` позволяет двигать числа со знаком. Сдвинуть их может и `shr`, вот только в регистре в результате этого получится белиберда.

Ты, наверное, уже понял, что вторым аргументом у всех инструкций сдвига указывается количество позиций, на которое нужно сдвинуть биты регистра. Обрати внимание: если указываешь это количество цифрой, то это может быть только единица. Если хочешь за раз сдвинуть сразу несколько битов, воспользуйся регистром `CL`.

Еще есть инструкции для циклического сдвига: `ror`, `rcr`, `rol` и `rcl`. В чем их особенность? Биты, выдвигаемые с одного конца, появляются с другой стороны. Циклический сдвиг вправо выполняется инструкцией `ror`, а влево — инструкцией `rol`. `rcr/rcl` делают то же самое, что `ror/rol`, только задействуют еще один дополнительный бит — `CF`. Добавляемый бит берется из `CF`, а выдвигаемый попадает в `CF`.

## INFO

У `shl` (логический сдвиг влево) есть синоним — `sal`. Две эти инструкции полностью идентичны — вплоть до того, что генерируются в один и тот же машинный код.

## Три логические инструкции плюс одна бесполезная

В 8088 доступны три логические инструкции: `and`, `or` и `xor`.

Инструкция `and` эквивалентна оператору `&` в Си и JavaScript; `or` — оператору `|`, а `xor` — оператору `^`.

Еще есть инструкция `not`, которая принимает только один параметр. Она инвертирует все биты указанного регистра. (`not al` эквивалентна оператору `~` в Си и JavaScript).

Кроме того, у 8088 есть инструкция `neg`, которая очень похожа на `not`, но только она делает не логическую инверсию, а арифметическую: меняет знак у заданного числа.

Еще в ассемблере есть инструкция, которая не делает совершенно ничего. Ты можешь вставить ее в любое место своей программы, и она никак не повлияет на ход выполнения. Конечно, за исключением того, что программа отработает чуть медленнее. Это инструкция `nop`. Можешь поэкспериментировать с ней. Вставь ее куда душе угодно после директивы `org`, и ты увидишь, как твоя программа увеличится ровно на один байт (это размер инструкции No OPeration), но работать будет без изменений.

## Знакомимся с инструкциями инкремента и декремента

Инструкции инкремента и декремента позволяют увеличивать или уменьшать на единицу значение регистра или значение переменной в памяти. Эти инструкции работают и с байтами (8 бит), и со словами (16 бит).

Тут мы:

1. загружаем в `AL` ASCII-код цифры ноль, то есть `0x30`;
2. показываем цифру на экране;
3. прибавляем к `AL` единицу;
4. повторяем шаги 2–3 до тех пор, пока в `AL` не окажется `0x39`;
5. показываем текущий символ и делаем все то же самое, что раньше, но в обратном порядке;
6. отнимаем от `AL` единицу;
7. выводим на экран то, что получилось;
8. повторяем до тех пор, пока в `AL` не окажется `0x30`.

В итоге программа выводит на экран вот такую строку: `012345678987654321`.

В этой программе есть еще одна новая для тебя инструкция — `cmp` (CoMPartion — сравнить). Она работает так же, как инструкция вычитания, но с одним значительным отличием: `cmp` не меняет значение регистра. Она меняет только биты регистра флагов (`Flags`).

Обычно `cmp` используется совместно с инструкциями условного перехода, такими как `je` (Jump if Equal — «прыгнуть, если равно»), `jne` (Jump if Not Equal — «прыгнуть, если не равно») и подобными.

## Пишем простую игрушку «Угадай число»

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

Как она будет работать? После запуска игры компьютер загадывает число, выводит на экран знак вопроса и ждет ответа игрока. Если число, введенное игроком, отличается от того, которое загадал компьютер, игра снова выводит знак вопроса. Когда игрок наконец угадывает число, программа печатает его на экране и добавляет смайлик (знак двоеточия и закрывающую скобку). Вот ассемблерный код, который воплощает описанную задумку.

Как компьютер загадывает число? Он считывает из порта `0x40` псевдослучайное число. Этот порт подключен к микросхеме таймера. Таймер без остановки отсчитывает такты процессора. Когда ты считываешь значение с его порта, то каждый раз получаешь псевдослучайное число в диапазоне от `0x00` до `0xFF`. Вот и весь секрет.

Теперь небольшой организационный момент. До сих пор, когда нам с тобой нужна была буква, мы задавали ее шестнадцатеричным ASCII-кодом. Но у компилятора NASM есть удобная особенность: ты можешь напечатать любой символ, заключить его в апострофы, и NASM сам преобразует его в ASCII-код.

Давай перепишем нашу игру, воспользовавшись этой особенностью NASM.

Согласись, так исходный код выглядит куда более читабельно.

## Выводы

Поздравляю, ты сделал небольшой шаг в освоении ассемблера! Теперь ты можешь создавать на нем небольшие игрушки. Та, которую мы с тобой сделали, занимает всего 70 байт.

### Инструкции и операторы

Если ты знаешь Си, Java или JavaScript, то вот небольшая табличка соответствия между ассемблерными инструкциями, которые ты сегодня освоил, и операторами из этих языков.

#### Оцени статью:

###### Что тебе понравилось больше всего?

Статья легкая и понятная Статья глубокая и подробная Интересуюсь этой темой Данные новые и актуальные Приятный стиль изложения

###### Что тебе не понравилось больше всего?

Статья поверхностная Статья слишком сложная Тема мне не интересна Данные устарели Неприятный стиль изложения
← Ранее Группировка Keeper взломала 570 интернет-магазинов
Далее → Появился бесплатный дешифровщик для файлов, пострадавших от ThiefQuest
16 комментариев к записи Погружение в ассемблер. Осваиваем арифметические инструкции

#### Последние взломы

### Краденые миллиарды и плохой рэп. Как арестовали супругов, которые отмывали деньги Bitfinex и снимали тиктоки

### HTB EarlyAccess. Эксплуатируем веб-уязвимости и мисконфиги Docker

### HTB Horizontall. Захватываем веб-сервер через уязвимости в Strapi и Laravel

### Препарируем P-Code. Как реверсить старый софт на Visual Basic

#### Компьютерные трюки

### Терминальное счастье. Колонка главреда

### Анатомия терминала. Осваиваем текстуальную графику

### Змей-стражник. Сигнализация для велосипеда из старого смартфона на Android

### M1 для хакера. Тестируем Homebrew, виртуалки, Docker и Node на новой архитектуре Apple

#### Свежие новости

- 2 дня назад

  ### Для критической уязвимости в Magento появился эксплоит, а Adobe исправила второй аналогичный баг

- 2 дня назад

  ### Вредоносные письма могут вывести Cisco Email Security Appliance из строя

- 2 дня назад

  ### WordPress-плагин с 3 000 000 установок позволял всем желающим скачивать бэкапы сайтов

- 3 дня назад

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

- 3 дня назад

  ### Google представила «легкую» Chrome OS Flex, подходящую для старых ПК

Вопросы по материалам и подписке: support@glc.ru
Отдел рекламы и спецпроектов: yakovleva.a@glc.ru
Контент 18+
Сайт защищен Qrator —
самой забойной защитой от DDoS в мире
Подписка для физлиц
Подписка для юрлиц
Реклама на «Хакере»
Контакты
Posting....
wpDiscuz