• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Погружение в ассемблер. Учимся работать с памятью

tabac

CPU register
Пользователь
Регистрация
30.09.2018
Сообщения
1 610
Решения
1
Реакции
3 332
В этой статье я познакомлю тебя с сегментной адресацией и сегментными регистрами, расскажу, как распределяется первый мегабайт оперативной памяти компьютера, затем мы обсудим получение прямого доступа к видеопамяти в текстовом режиме, а самое главное — поностальгируем по фильму «Хакер»! Под конец напишем психоделическую программу, которой позавидовали бы его герои.


Знакомимся с сегментными регистрами

Для начала разберемся, что такое сегментные регистры. Процессор 8088 умеет адресовать один мегабайт оперативной памяти, несмотря на то что регистры у него 16-битные. В норме 16 битами можно адресовать только 64 Кбайт. И как же тогда выкручивается 8088? Как ему удается адресовать целый мегабайт? Для этого в 8088 есть сегментные регистры! Четыре сегментных регистра: CS, ES, DS и SS, по 16 бит каждый. У каждого из этих регистров есть свое назначение.

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

Регистры DS и ES указывают на сегмент данных. К этим регистрам процессор обращается, когда выполняемая инструкция считывает или сохраняет данные. Имей в виду: DS используется чаще, чем ES. ES обычно вступает в игру, когда ты обрабатываешь массивы данных, индексируя их регистром DI.

Регистр SS указывает на сегмент стека. К этому регистру процессор обращается, когда выполняет инструкции, взаимодействующие со стеком: push, pop, call и ret.

Значения, хранимые в сегментных регистрах, — это базовый адрес, поделенный на 16. Если в CS записано значение 0x0000, процессор будет считывать инструкции из области памяти 0x00000 — 0x0FFFF. Если в регистре CS записано значение 0x1000, то процессор будет считывать инструкции из области памяти 0x10000 — 0x1FFFF.

Если тебе надо адресоваться в какой-то другой сегмент, а не тот, который будет задействован по умолчанию, напиши этот сегмент в префиксе к инструкции, которую используешь.
src01.jpg

Поместить какое-то число в сегментные регистры напрямую нельзя. Для этого надо:
  • либо сначала записать число в какой-нибудь регистр и уже этот регистр присвоить сегментному регистру;
  • либо воспользоваться парой инструкций push/pop;
  • либо воспользоваться инструкциями lds/les.
src02.jpg

Процессор 8088 настолько лоялен, что позволит тебе даже вот такую инструкцию: pop cs. Но только имей в виду, что это нарушит поток выполнения инструкций. В более новых процессорах, начиная с 80286, такую диверсию сделать уже не получится. И слава богу!

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


Как ПК распределяет память
Понимание того, как распределяется память в ПК, поможет тебе делать разные интересные вещи.
src03.jpg

Начало загрузки у всех компьютеров одинаковое: они сначала переходят в текстовый цветной режим 80x25. К видеопамяти экрана, который работает в таком режиме, можно обращаться напрямую, через вот этот диапазон адресов: 0xB8000 — 0xB8FFF.

Первый байт диапазона — это первый символ в верхнем левом углу экрана. Второй байт — это цвет фона под символом и цвет самого символа. Затем (третьим байтом) идет второй символ. И так для всех 25 строк по 80 символов каждая.

Исторический факт. IBM PC образца 1981 года поставлялся в двух модификациях: с монохромным режимом и с цветным режимом. Тогдашним разработчикам игрушек приходилось придумывать разные эвристики, чтобы понять, какая у компьютера графика: монохромная или цветная. Несколько старых добрых игрушек для этого писали какую-нибудь цифру по адресу 0xB8000 и считывали ее обратно — чтобы узнать, в каком режиме сейчас идет работа: в цветном или в монохромном.


Прямой доступ к видеопамяти в текстовом режиме
Перед тем как мы сможем напрямую обращаться к видеопамяти экрана, то есть без использования сервисов BIOS, надо задать нужный нам видеорежим.
src04.jpg

В этом режиме видеопамять экрана доступна по адресу 0xB8000. Теперь ты можешь легко получить доступ к ней. Примерно так, как в коде ниже.
src05.jpg

Обрати внимание, что после того, как ты выполнил этот кусок кода, ты не сможешь обращаться к переменным, которые сохраняешь в сегменте кода, как мы это делали в примерах из прошлых уроков. Потому что DS и ES теперь нацелены не на сегмент кода, а на видеопамять.

Как же быть? Как вариант, можно перед тем, как нацеливать DS и ES на видеопамять, сохранить их значение на стеке. Потом нацелиться на видеопамять и вывести нужные данные на экран. А после этого снять со стека сохраненный указатель и снова поместить его в DS и ES.
src06.jpg


Видеопамять, которая отображается на экран, организована вот таким вот образом.
screen.jpg


Здесь на каждый символ отведено по два байта. Первый байт — это ASCII-код буквы. А второй — цвет фона и символа.
format.jpg


Кодировка цветов следующая.
colors.jpg


А теперь давай посмотрим все это на живом примере. Напиши и выполни вот такой код.
hello.jpg


Мы здесь задействовали парочку новых инструкций: cld и stosw. Инструкция cld очищает флаг направления. Зачем нужен этот флаг? Некоторые продвинутые инструкции (многошаговые) используют его, чтобы понять, что нужно делать с регистрами SI и DI после очередного шага выполнения: увеличивать их или уменьшать.

Инструкция stosw записывает значение регистра AX по адресу ES:DI и увеличивает регистр DI на два (размер слова — сдвоенного байта). Обрати внимание: если флаг направления установлен в единицу (это можно сделать инструкцией sed), то та же самая инструкция будет не увеличивать DI, а уменьшать. Тоже на два. Поэтому мы сначала используем cld, поскольку не знаем, какое на данный момент значение у флага направления.

Всякий раз, когда мы загружаем AX, у нас AL содержит саму букву, которую надо нарисовать на экране, а AH — цвет символа и цвет его фона (в данном случае синий фон и разные цвета для каждой буквы).

Когда мы пишем слово (сдвоенный байт) в память, младший байт всегда пишется первым (в данном случае значение регистра AL), а старший байт идет следом (в данном случае значение регистра AH).

Таким образом, после того как весь наш текст записан в видеопамять, память будет заполнена вот так.
example.jpg


Теперь ты умеешь рисовать на экране цветные буквы. При желании можешь поэкспериментировать с символами псевдографики, которые доступны в BIOS.

Кстати, на всякий случай имей в виду, что в 8088 еще есть инструкция stosb. Она работает так же, как stosw, но только не со словами (сдвоенными байтами), а с байтами. stosb пишет значение регистра AL по адресу ES:DI и увеличивает DI на единицу (или уменьшает, если флаг направления установлен в единицу).


Рисуем психоделические круги

А теперь переходим к самому интересному! Сейчас напишем программу, которая рисует психоделические круги, как в фильме «Хакер». Помнишь там эпизод, где наши с тобой коллеги восхищаются «миллионом психоделических оттенков» на ноуте Кислотного Ожога?
01_A8upwIM.jpg


Мы нарисуем то же самое, но в классическом текстовом режиме, в разрешении 80x25 с 16 цветами для фона и текста. Итак, приступим.

Сначала инициализируем видеорежим и настраиваем сегменты на видеопамять экрана.
src07.jpg

Потом инициализируем таймер, который поможет добавить динамики к нашей психоделической анимации. Зачем он нам? Чтобы на первых 32 кадрах анимации рисовать увеличивающиеся круги, а на остальных 32 — уменьшающиеся.
src08.jpg


Что тут происходит? Сервис 0x00 прерывания 0x1A считывает, сколько тиков прошло с того момента, как была загружена система. Результат возвращается в 32-битном виде, помещается в пару регистров: CX:DX.

Поскольку для наших нужд надо вести счет только от 0 до 63 и затем обратно, мы проверяем шестой бит (0x40), и если он установлен в единицу, то добавляем к текущему значению счетчика знак минус.

Затем инструкцией and al, 0x3F выделяем шесть младших битов и при помощи вычитания приводим знаковое 8-битное значение к диапазону -32..+31, которое затем расширяем до слова (до сдвоенного байта). Результат помещаем в CX.

А теперь самое интересное. Вычисляем параметры текущего шага анимации и отрисовываем ее.
src09.jpg

Что мы тут делаем? В DI у нас будет храниться текущая позиция на экране, куда выводить символ. Сначала сбрасываем ее в 0, чтобы рисовать начиная с левого верхнего угла. В DH и DL будем хранить номер строки и столбца. А в BX у нас будет адрес таблицы синусов (ее покажу чуть позже).

Теперь, когда мы проинициализировали все нужные переменные, нам надо вычислить с помощью таблицы синусов, каким цветом отрисовывать символ (у нас это будет звездочка) в очередной позиции экрана. Чтобы в итоге получились круги.

Для этого делаем вот что. Берем номер текущей строки (из DH). Умножаем его на два, чтобы круги были кругами, а не овалами. По этому значению извлекаем синус из таблицы символов (смотри инструкцию xlat).

Что это за инструкция такая, xlat? Ее мнемоника — это сокращение от слова translate. Она считывает байт с адреса BX+AL и помещает его в регистр AL. То есть делает то же самое, что инструкция mov, al, [bx+al], но только более лаконично. Перед тем как обращаться к xlat, надо сначала загрузить в BX адрес таблицы, а в AL — индекс нужного нам элемента из этой таблицы.

Обрати внимание на префикс CS, который стоит перед xlat. Он говорит процессору, что сейчас при считывании данных надо обращаться к кодовому сегменту. Без префикса CS данные будут считаны из сегмента, на который указывает регистр DS. И получится белиберда, потому что DS сейчас указывает на экран, а не на код, где размещена таблица синусов. После того как мы извлекли значение синуса, сохраняем его на стеке (смотри инструкцию push ax).

На этом полдела сделано. Мы взяли номер текущей строки (DH) и вычислили по нему синус. Дальше делаем то же самое для номера текущего столбца (DL). В итоге у нас получается два значения синуса. Одно сейчас хранится в регистре AX, а другое лежит на стеке.

Поэтому мы снимаем со стека предыдущее значение синуса (смотри инструкцию pop DX) и складываем с текущим значением синуса, которое сейчас хранится в AX. И еще плюсуем к ним текущее время (в тиках, от момента включения компьютера). Младший байт полученного результата — это и есть искомое значение цвета (цвет фона + цвет текста).

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

Итак, нужный цвет мы вычислили и теперь выводим звездочку на экран: mov [di], ax. Затем добавляем к DI двойку, чтобы перейти на следующую позицию экрана — где будем рисовать новую звездочку, но уже другим цветом. Ты ведь помнишь, что у нас на каждый символ отводится по два байта?

Наконец, заключительный штрих: корректируем номера текущей строки и текущего столбца и переходим к следующей итерации цикла.
src10.jpg

Что мы тут делаем? Сначала берем номера текущих строки и столбца (их мы сохранили на стеке). Помещаем их в DX. Затем прибавляем единицу к DL. Если результат получился меньше 80 (столько столбцов у нас на экране), то повторяем цикл. А если досчитали до 80, то переходим на следующую строку: увеличиваем DH на единицу, а DL обнуляем. Но делаем этот последний шаг, только если не досчитали до 25. А если в DH у нас уже 25, то переходим в самое начало программы (смотри метку @@main_loop) и начинаем все заново.

В итоге получается анимация с психоделическими кругами, которые то растут, то уменьшаются.
psy-screen.png

Кадр из нашей программы


Автор @vedacoder Антон Карев
хакер.ру
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Когда видешь кодера который могёт в ассемблере, такое чувство что мамонта в живую увидел)
хоть асссемблер и стар но все го минуси невелируююса один плюс 100% виполнение програми по твоему сценарию и отсутствие всяого рода багов
 
Когда видешь кодера который могёт в ассемблере, такое чувство что мамонта в живую увидел)
на самом деле нет легче языка чем асм.
хоть асссемблер и стар но все го минуси невелируююса один плюс 100% виполнение програми по твоему сценарию и отсутствие всяого рода багов
я всем говорю практически то-же самое. что написал то и скомпилировалось, без всяких неожиданностей.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Прошу подсказать по поводу языка asm, на работе один динозавр заставил выучить азы и работать с pic.
Сейчас думаю, где можно использовать в наше время эти знания помимо микроконтроллеров?
Вопрос больше к тому стоит ли учить assembler?
И если да, то зачем?

Спасибо)
 
Прошу подсказать по поводу языка asm, на работе один динозавр заставил выучить азы и работать с pic.
Сейчас думаю, где можно использовать в наше время эти знания помимо микроконтроллеров?
Вопрос больше к тому стоит ли учить assembler?
И если да, то зачем?

Спасибо)
На мой взгляд, знание ассемблера позволяет понимать логику и функционования програмного обеспечения на самом низком уровне, куда попросту не пробраться языкам высокого уровня. Написания драйверов, малвари - становиться невозможным хотя бы без базового понимания этого языка.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Вопрос больше к тому стоит ли учить assembler?
Зависит от того, чем планируете заниматься. Для малвари и прочего лоу левела желательно, не кодить - а просто понимать, как работает вызов функций, что такое регистры , стек. Для белой работы.. наверное, все таки нет. Я считал, что нужно, но форумные дебаты убедили меня, что нужно знать ООП и последние стандарты шарпа/плюсов. А какой-то асм и процедурное программирование - архаизм, никому не нужный.
 
Зависит от того, чем планируете заниматься. Для малвари и прочего лоу левела желательно, не кодить - а просто понимать, как работает вызов функций, что такое регистры , стек. Для белой работы.. наверное, все таки нет. Я считал, что нужно, но форумные дебаты убедили меня, что нужно знать ООП и последние стандарты шарпа/плюсов. А какой-то асм и процедурное программирование - архаизм, никому не нужный.
полагаю, что для написания по на производстве иногда нужен ассемблер
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Зависит от того, чем планируете заниматься. Для малвари и прочего лоу левела желательно, не кодить - а просто понимать, как работает вызов функций, что такое регистры , стек. Для белой работы.. наверное, все таки нет. Я считал, что нужно, но форумные дебаты убедили меня, что нужно знать ООП и последние стандарты шарпа/плюсов. А какой-то асм и процедурное программирование - архаизм, никому не нужный.
Спасибо, теперь понимаю, что знания asm имеют ценность только как подспорье для изучения нового.
Вообще сейчас присматриваюсь к вариантам белой работы в проге. Еще по ООП и последним стандартам, то важно еще базы данных хоть немного знать и еще парочку технологий.

Если и дальше продолжу разбираться в asm то придется еще прошаривать радиотехнические приблуды, длинну дорожек на высоких частотах уже учитываю. Если вникнуть, то выходит очень много низкоуровневой шелухи, не учитывать которую не выйдет (если конечно не на кого скинуть).
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Не хочу начинать тысячный холивар про асмы и прочее. Надоело уже.
Мне асм пригодился, я понимаю как работает прога, и могу реверсить чужой говнокод. Большинству мб и не пригодится, не знаю. Какой-то докер актуален или лямбды или какую еще ерунду изучают на хабре.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Для ЛЮБОГО грамотного программиста-очевидно нужен обязательно. Причем не так важно какого прикладного уровня прграммист. Если не будете знать ассемблер, то не будете понимать как работают программы, и, соответственно не сможете писать эффективный и безопасный код, на любом языке, так как в итоге каждая программа компиллируется или интерпретируется в ассемблер, а затем в машинный код. Но есть области, где без знания ассембрела вообще никак-это информационная безопасность(малварь, вирусы, итд), реверс инжениринг, многие отрасли в промышленности, итд.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Не ужели асмом кто-то еще пользуется, не перевелись еще умельцы любящие писать тонны строк кода, а так респект за статью
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Не ужели асмом кто-то еще пользуется, не перевелись еще умельцы любящие писать тонны строк кода, а так респект за статью
Не перевелись, и врядли переведутся, пока существуют как минимум такие направеления как реверс инжениринг, вирусо- и антивирусописание, программирование микроконтроллеров и напримение встроенных систем, системное программирование вцелом, программирование в промышленных системах, а также пока существуют программисты, кому небезразлично качество написанного кода-))

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

С уважением,
и не поспоришь, все по полочкам
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх