Современные информационные технологии / Вычислительная техника и программирование

преп. Бартосик Ф.М., к.т.н. Зартенова Л.Г., 

Карагандинский государственный технический  университет, Казахстан

 

АССЕМБЛЕР НАВСЕГДА

 

 

Наверное, не существует более таинственного и в тоже время столь простого языка,  как Assembler.

Обычно пугаются его начинать изучать либо наслушавшись некомпетентных людей, либо начитавшись подобных книг. О том, что это очень сложно, говорят и пишут люди, у которых в свое время не хватило смелости (и/или ума) попытаться вникнуть в "машинные коды", "прерывания", "порты ввода-вывода" и прочую низкоуровневую "бессмыслицу", с которой рано или поздно сталкивается любой профессиональный программист. Однако необходимо четко осознать, что принципы функционирования компьютера и некоторые основы низкоуровневого программирования относятся к категории элементарных знаний, владеть которыми обязан КАЖДЫЙ программист - вне зависимости от среды, в которой он разрабатывает свои приложения...  И, тем не менее, поверьте, программировать, используя регистры процессора и прямое обращение к основной и видеопамяти, является не только простым, но даже очень увлекательным занятием.

Дальнейшее свое изложение постараюсь вести на очень простом, всем понятном языке, ориентируясь на начинающих программистов, поскольку именно им хочу привить любовь к искусству программирования на низком уровне. Несколько слов в отношении последнего термина. Многие люди думают, что классификация на языки высокого и низкого уровня – это конечная инстанция. Ассемблерщики должно четко понимать, что существует программирование на высоком уровне (с использованием макроконструкций, классов и т.п.), на среднем уровне (с использованием BIOS и DOS функций, так называемых прерываний; и Windows функций - WinAPI) и на низком уровне (программирование «железа» напрямую, т.е. через порты, работа с жестким диском  напрямую и т.п.).

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

Приведу  несколько примеров по программированию в DOS.

Написав игрушку «питон» (или «змейка», если угодно) – получил файл размером 606 байт. Сделано в текстовом режиме, но выглядит довольно прилично. И глядя на нее с “сегодняшних” позиций понимаю, что можно было бы и еще уменьшить (оптимизировать) код этой игры.

Теперь пример из области графических режимов. Возникло желание оживить картинку – “Часы с маятником”. Потребовалась дополнительная подготовка фрагментов в Adobe Photoshop. Были подготовлены отдельные фрагменты (и сохранены в виде пяти файлов): 3 стрелки, маятник, и часы без стрелок и маятника (назовем его главный). Возникает недоумение: где еще два фрагмента – “чистый” циферблат (без стрелочек) и “чистое” место под маятником (без маятника)? Все очень просто – нет нужды хранить это в виде файлов на носителе. При загрузке главного рисунка в память эти фрагменты брались непосредственно с него и размещались в памяти по своим местам. А дальше, как вы понимаете, начиналась прорисовка на экране по принципу слоев. Для красоты был реализован следующий момент: на стекле циферблата и на стекле перед маятником были наклейки, и при выводе стрелок и маятника – они выводились позади наклеек. Очистка от старого положения (“кадра”) стрелок и маятника проводилась очевидным образом: рисовались “чистые места (см. выше)”, а затем стрелки и маятник.

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

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

Думаю, пришло время слегка возбудить любителей писать вирусы. Примеров с вирусами приводить не стану – это не корректно, но считаю, что упомянуть о связке: ассемблер-вирус, просто необходимо. Представим классическую ситуацию, когда вирус присоединятся в конец файла. Просто дописать что-то к какому-то файлу, не составляет сложности на любом языке. Но вот как активизировать вирус, т.е. как передать управление на вирусное тело? На языке ассемблер написать переход на нужное количество байт – это его естественное поведение. После выполнения вирусом своих действий, нужно вернуть управление «настоящему» коду зараженной программы. Причем сделать все это нужно достаточно быстро: чтобы никто ничего не заметил. Если писать вирусное тело в начале файла, возникает другая ситуация: «настоящий» код будет расположен не по своим адресам, и выполняться не будет. Требуется понимание организации оперативной памяти, что такое Memory Control Block (MCB), каким образом используются сегменты памяти. Работать со всем этим на языках высокого уровня опять таки затруднительно. Или более существенный вариант: вирусное тело хочет остаться резидентом в памяти, но еще нужно при этом суметь выполнить «настоящий» код. Вирусу в памяти нужно выполнять довольно “веселые” маневры: сегменты создаются и удаляются, перемещение кода, как своего, так и «настоящего», редактирование MCB, активизация новых созданных Program Segment Prefix (PSP).

Теперь о резидентах и перехвате прерываний. Наиболее значимым является перехват прерываний таймера и клавиатуры. Опять же возвращаясь к вирусному резиденту: первый случай может понадобиться, если необходимо активизировать резидента через определенные промежутки времени, во втором – это может быть клавиатурный шпион. Разумеется, можно перехватывать и все остальное: мышь, принтер, монитор и т.п., или функции DOS, скажем по работе с файлами.

Однако, из всего этого «негатива», можно извлекать и вполне законную разумную выгоду. Думаю, что многим попадались DOS-программы, выполненные в графическом режиме. И конечно возникала ситуация, когда требовалось получить снимок экрана. В графическом режиме, а также  текстовом полноэкранном, нажатие клавиши <PrintScreen> ничего не дает. Так кто же мешает написать резидент, реагирующий на определенную клавишу (или комбинацию клавиш) и сохраняющий снимок экрана в “BMP”-файл. Выполнять прямое обращение к видеобуферу на ассемблере – одно удовольствие. Ну а разобраться в заголовке “BMP”-файла не составляет труда. Могу даже порекомендовать книгу Кулакова «Программирование на аппаратном уровне». Там этот заголовок рассмотрен четко и понятно.

В заключении про DOS хочу сказать, что эта система даже сегодня еще не “умерла”. Заходя в некоторые банки, я видел, что она там интенсивно используется, и никто не жалуется. Представьте себе, еще пишут базы данных под эту систему, и из-под этой системы можно работать в сети. Скажете: «антиквариат»! Неужели вы думаете, что в военной, космической и других подобных отраслях, используется Microsoft Windows. Данная ОС, конечно красивая media-вещь. Но там, где очень жесткие требования к надежности и быстродействию системы, где ошибка системы опасна для здоровья и жизни людей, эта система не подходит. В настоящее время в этих отраслях активно переходят на Unix-подобные системы, но в недалеком прошлом пользовались DOS.

Так зачем же нужен язык ассемблера? Самой простой и убедительный ответ: "Затем, что это язык процессора и, следовательно, он будет нужен до тех пор, пока будут существовать процессоры". Более точный ответ на этот вопрос содержит в себе то понятие, что ассемблер может очень даже сильно пригодится для оптимизации кода программ, написания драйверов, трансляторов, программирования некоторых внешних устройств и т.д. Кроме того, программирование на ассемблере дает ощущение власти над компьютером, а жажда власти — один из сильнейших инстинктов человека. И еще: язык ассемблера - язык универсальный. Действительно, компьютеры на базе микропроцессоров Intel широко распространились по всему свету. И какая бы операционная система ни работала на компьютере - OS/2, Windows, Unix или старенькая MS DOS, язык останется тем же, изменится лишь взаимодействие с операционной системой.

В общем: добро пожаловать в мир, где программист - хозяин компьютера, а не наоборот, - в мир низкоуровневого программирования.