Мысли о программирование на ассемблере. Архив рубрики: Книги по Assembler

Написание ОС-загрузчиков, драйверов, переписывание области памяти и другие задачи по работе с ЭВМ реализовываются с помощью ассемблера. Выбранные книги по ассемблеру помогут понять принцип работы машинно-ориентированного языка и освоить его.

1. Ревич Ю. – Практическое программирование микроконтроллеров Atmel AVR на языке ассемблера, 2014 г.

«Свежая кровь» в области программирования микроконтроллеров. Подробно изложены особенности Atmel AVR, есть перечень команд и готовые рецепты – ассемблер на примерах. Хорошая вещь для радиолюбителей и инженерно-технических работников, хотя подойдет и начинающим кодерам: затронуты история, семейства и возможности МК AVR. Стоит отметить, что введение лаконичное, быстро перетекающее в суть, поэтому сетовать на лирику не придется.

2. Калашников О. – Ассемблер – это просто. Учимся программировать, 2011 г.

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

3. Аблязов Р. – Программирование на ассемблере на платформе x86-64, 2011 г.

Акцент делается на работе процессора в защищенном режиме и long mode. Это незаменимая база для программирования в Win32 и Win64, которая затрагивает команды ассемблера, прерывания, механизмы трансляции и защиты с учетом режимных отличий. Рассматривается разработка оконных приложений и драйверов. Данный ассемблер учебник подойдет начинающим кодерам и тем, кто сразу перешел к программированию на ассемблере, но плохо разобрался в аппаратной платформе x86-64.

4. Столяров А. – Программирование на языке ассемблера NASM для ОС Unix, 2011 г.

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

Эту идею мы вынашивали долго. Наверное, несколько лет мы штурмовали ее со всех сторон, и всякий раз нам что-нибудь мешало. С одной стороны, ассемблер - это круто настолько, насколько вообще может быть круто для нашего читателя-хакера (крякера, реверсера) умение общаться с компьютером на его языке. С другой стороны - актуальных руководств по асму, в том числе издания этого века, достаточно, а времена нынче либеральные, веб-хакеры и любители JS могут нас не понять и не одобрить. 🙂 Точку в споре физиков, лириков, старообрядцев, никониан, веб-хакеров и тру-крякеров поставил успех . Оказалось, что сейчас, в XXI веке, тру-крякеры все еще не сдали своих позиций и нашим читателям это интересно!

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

Но, каким бы видом программирования мы ни занимались, успех зависит от практических навыков вкупе со знанием фундаментальных основ и теории. Теория и практика, изучение и труд - вот краеугольные камни, на которых основывается успех.

В последнее время ассемблер незаслуженно находится в тени других языков. Обусловлено это глобальной коммерциализацией, направленной на то, чтобы в максимально короткие сроки получить как можно большую прибыль от продукта. Иными словами, массовость взяла верх над элитарностью. А ассемблер, по моему мнению, ближе к последнему. Гораздо выгоднее в сравнительно небольшие сроки поднатаскать ученика в таких, например, языках, как С++, С#, PHP, Java, JavaScript, Python, чтобы он был более-менее способен создавать ширпотребный софт, не задаваясь вопросами, зачем и почему он так делает, чем выпустить хорошего специалиста по ассемблеру. Примером тому служит обширнейший рынок всевозможных курсов по программированию на любом языке, за исключением ассемблера. Та же тенденция прослеживается как в преподавании в вузах, так и в учебной литературе. В обоих случаях вплоть до сегодняшнего дня большая часть материала базируется на ранних процессорах серии 8086, на так называемом «реальном» 16-битном режиме работы, операционной среде MS-DOS! Возможно, что одна из причин в том, что, с одной стороны, с появлением компьютеров IBM PC преподавателям пришлось перейти именно на эту платформу из-за недоступности других. А с другой стороны, по мере развития линейки 80х86 возможность запуска программ в режиме DOS сохранялась, что позволяло сэкономить деньги на приобретение новых учебных компьютеров и составление учебников для изучения архитектуры новых процессоров. Однако сейчас такой выбор платформы для изучения совершенно неприемлем. MS-DOS как среда выполнения программ безнадежно устарела уже к середине девяностых годов, а с переходом к 32-битным процессорам, начиная с процессора 80386, сама система команд стала намного более логичной. Так что бессмысленно тратить время на изучение и объяснение странностей архитектуры реального режима, которые заведомо никогда уже не появятся ни на одном процессоре.

Что касается выбора операционной среды для изучения ассемблера, то, если говорить о 32-битной системе команд, выбор сравнительно невелик. Это либо операционные системы Windows, либо представители семейства UNIX.

Также следует сказать несколько слов о том, какой именно ассемблер выбрать для той или другой операционной среды. Как известно, для работы с процессорами х86 используются два типа синтаксиса ассемблера - это синтаксис AT&T и синтаксис Intel. Эти синтаксисы представляют одни и те же команды совершенно по-разному. Например, команда в синтаксисе Intel выглядит так:

Mov eax,ebx

В синтаксисе же AT&T уже будет иной вид:

Movl %eax,%ebx

В среде ОС UNIX более популярен синтаксис типа AT&T, однако учебных пособий по нему нет, он описывается исключительно в справочной и технической литературе. Поэтому логично выбрать ассемблер на основе синтаксиса Intel. Для UNIX-систем есть два основных ассемблера - это NASM (Netwide Assembler) и FASM (Flat Assembler). Для линейки Windows популярностью пользуются FASM и MASM (Macro Assembler) от фирмы Microsoft, и также существовал еще TASM (Turbo Assembler) фирмы Borland, которая уже довольно давно отказалась от поддержки собственного детища.

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

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

Что такое ассемблер?

Само слово ассемблер (assembler) переводится с английского как «сборщик». На самом деле так называется программа-транслятор, принимающая на входе текст, содержащий условные обозначения машинных команд, удобные для человека, и переводящая эти обозначения в последовательность соответствующих кодов машинных команд, понятных процессору. В отличие от машинных команд, их условные обозначения, называемые также мнемониками , запомнить сравнительно легко, так как они представляют собой сокращения от английских слов. В дальнейшем мы будем для простоты именовать мнемоники ассемблерными командами. Язык условных обозначений и называется языком ассемблера .

На заре компьютерной эры первые ЭВМ занимали целые комнаты и весили не одну тонну, имея объем памяти с воробьиный мозг, а то и того меньше. Единственным способом программирования в те времена было вбивать программу в память компьютера непосредственно в цифровом виде, переключая тумблеры, проводки и кнопочки. Число таких переключений могло достигать нескольких сотен и росло по мере усложнения программ. Встал вопрос об экономии времени и денег. Поэтому следующим шагом в развитии стало появление в конце сороковых годов прошлого века первого транслятора-ассемблера, позволяющего удобно и просто писать машинные команды на человеческом языке и в результате автоматизировать весь процесс программирования, упростить, ускорить разработку программ и их отладку. Затем появились языки высокого уровня и компиляторы (более интеллектуальные генераторы кода с более понятного человеку языка) и интерпретаторы (исполнители написанной человеком программы на лету). Они совершенствовались, совершенствовались - и, наконец, дошло до того, что можно просто программировать мышкой.

Таким образом, ассемблер - это машинно ориентированный язык программирования, позволяющий работать с компьютером напрямую, один на один. Отсюда и его полная формулировка - язык программирования низкого уровня второго поколения (после машинного кода). Команды ассемблера один в один соответствуют командам процессора, но поскольку существуют различные модели процессоров со своим собственным набором команд, то, соответственно, существуют и разновидности, или диалекты, языка ассемблера. Поэтому использование термина «язык ассемблера» может вызвать ошибочное мнение о существовании единого языка низкого уровня или хотя бы стандарта на такие языки. Его не существует. Поэтому при именовании языка, на котором написана конкретная программа, необходимо уточнять, для какой архитектуры она предназначена и на каком диалекте языка написана. Поскольку ассемблер привязан к устройству процессора, а тип процессора жестко определяет набор доступных команд машинного языка, то программы на ассемблере не переносимы на иную компьютерную архитектуру.

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

Синтаксис

Общепринятого стандарта для синтаксиса языков ассемблера не существует. Однако большинство разработчиков языков ассемблера придерживаются общих традиционных подходов. Основные такие стандарты - Intel-синтаксис и AT&T-синтаксис .

Общий формат записи инструкций одинаков для обоих стандартов:

[метка:] опкод [операнды] [;комментарий]

Опкод - это и есть собственно ассемблерная команда, мнемоника инструкции процессору. К ней могут быть добавлены префиксы (например, повторения, изменения типа адресации). В качестве операндов могут выступать константы, названия регистров, адреса в оперативной памяти и так далее. Различия между стандартами Intel и AT&T касаются в основном порядка перечисления операндов и их синтаксиса при разных методах адресации.

Используемые команды обычно одинаковы для всех процессоров одной архитектуры или семейства архитектур (среди широко известных - команды процессоров и контроллеров Motorola, ARM, x86). Они описываются в спецификации процессоров.

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

Краткое описание языков Ассемблера

Все языки программирования разделяются по уровням: низкий и высокий. Любой из синтаксической системы «семейки» Ассемблера отличается тем, что объединяет сразу некоторые достоинства наиболее распространенных и современных языков. С другими их роднит и то, что в полной мере можно использовать систему компьютера.

Отличительной особенностью компилятора является простота в использовании. Этим он отличается от тех, которые работают лишь с высокими уровнями. Если взять во внимание любой такой язык программирования, Ассемблер функционирует вдвое быстрее и лучше. Для того чтобы написать в нем легкую программу, не понадобится слишком много времени.

Кратко о структуре языка

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

В отличие от других языков программирования, Ассемблер использует вместо адресов для записи ячеек памяти определенные метки. Они с процессом выполнения кода переводятся в так называемые директивы. Это относительные адреса, которые не влияют на работу процессора (не переводятся в машинный язык), а необходимы для распознавания самой средой программирования.

Для каждой линейки процессора существует своя При таком раскладе правильным будет любой процесс, в том числе и переведенный

Язык Ассемблера имеет несколько синтаксисов, которые будут рассмотрены в статье.

Плюсы языка

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

Драйвера, операционные системы, BIOS, компиляторы, интерпретаторы и т. д. - это все программа на языке Ассемблера.

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

Минусы языка

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

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

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

Команды языка

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


Использование директив

Программирование микроконтроллеров на языке (Ассемблер это позволяет и прекрасно справляется с функционированием) самого низкого уровня в большинстве случаев заканчивается удачно. Лучше всего использовать процессоры с ограниченным ресурсом. Для 32-разрядной техники данный язык подходит отлично. Часто в кодах можно заметить директивы. Что же это? И для чего используется?

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


Происхождение названия

Благодаря чему получил название язык - "Ассемблер"? Речь идет о трансляторе и компиляторе, которые и производят зашифровку данных. С английского Assembler означает не что иное, как сборщик. Программа не была собрана вручную, была использована автоматическая структура. Более того, на данный момент уже у пользователей и специалистов стерлась разница между терминами. Часто Ассемблером называют языки программирования, хотя это всего лишь утилита.

Из-за общепринятого собирательного названия у некоторых возникает ошибочное решение, что существует единый язык низкого уровня (или же стандартные нормы для него). Чтобы программист понял, о какой структуре идет речь, необходимо уточнять, для какой платформы используется тот или иной язык Ассемблера.

Макросредства

Языки Ассемблера, которые созданы относительно недавно, имеют макросредства. Они облегчают как написание, так и выполнение программы. Благодаря их наличию, транслятор выполняет написанный код в разы быстрее. При создании условного выбора можно написать огромный блок команд, а проще воспользоваться макросредствами. Они позволят быстро переключаться между действиями, в случае выполнения условия или невыполнения.

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

Assembly Language for x86 Processors, 7e is suitable for undergraduate courses in assembly language programming and introductory courses in computer systems and computer architecture. Proficiency in one other programming language, preferably Java, C, or C++, is recommended.

Written specifically for 32- and 64-bit Intel/Windows platform, this complete and fully updated study of assembly language teaches students to write and debug programs at the machine level. This text simplifies and demystifies concepts that students need to grasp before they can go on to more advanced computer architecture and operating systems courses. Students put theory into practice through writing software at the machine level, creating a memorable experience that gives them the confidence to work in any OS/machine-oriented environment.

Изложены принципы функционирования, особенности архитектуры и приемы программирования микроконтроллеров Atmel AVR. Приведены готовые рецепты для программирования основных функций современной микроэлектронной аппаратуры: от реакции на нажатие кнопки или построения динамической индикации до сложных протоколов записи данных во внешнюю память или особенностей подключения часов реального времени. Особое внимание уделяется обмену данными микроэлектронных устройств с персональным компьютером, приводятся примеры программ. В книге учтены особенности современных моделей AVR и сопутствующих микросхем последних лет выпуска. Приложение содержит основные параметры микроконтроллеров AVR, перечень команд и тексты программ для них, а также список используемых терминов и аббревиатур. Для учащихся, инженерно-технических работников и радиолюбителей. 3-е издание, ипсравленное.

В данной книге речь идет о работе процессора в двух его основных режимах: защищенном режиме и 64-битном, который также называют long mode («длинный режим»). Также помимо изложения принципов и механизмов работы процессора в защищенном и 64-битном режимах, речь пойдет о программировании на ассемблере в операционных системах семейства Windows, как в 32-битных, так и 64-битных версиях. Рассматривается не только разработка обычных приложений для операционных систем Windows, но и разработка драйверов на ассемблере. При написании книги уделялось большое внимание именно практической составляющей, т.е. изложение материала идет только по делу и только то, что необходимо знать любому системному и низко-уровневому программисту. Последний раздел книги посвящен принципам работы многопроцессорных систем, а также работе с расширенным программируемым контроллером прерываний (APIC).
На диске, прилагаемом к книге, находятся полные исходные коды примеров к книге, а также дополнительные программы и материалы.

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

Подробно и доходчиво объясняются все основные вопросы программирования на ассемблере. Рассмотрены команды процессоров Intel, 16- и 32-разрядные регистры, основы работы с сопроцессором, сегментация памяти в реальном масштабе времени, управление клавиатурой и последовательным портом, работа с дисками и многое другое. Описано, как разработать безобидный нерезидентный вирус и антивирус против этого вируса, как написать файловую оболочку (типа Norton Commander или FAR Manager) и как писать резидентные программы.
Каждая глава состоит из объяснения новой темы, описания алгоритмов программ, многочисленных примеров и ответов на часто задаваемые вопросы. Во второе издание внесены исправления и добавлены новые примеры.
Компакт-диск содержит исходные коды всех примеров, приведенных в книге, с подробными описаниями.

Описание книги Практическое программирование микроконтроллеров Atmel AVR на языке ассемблера :
Изложены принципы функционирования, особенности архитектуры и приемы программирования микроконтроллеров Atmel AVR. Приведены готовые рецепты для программирования основных функций современной микроэлектронной аппаратуры: от реакции на нажатие кнопки или построения динамической индикации до сложных протоколов записи данных во внешнюю память или особенностей подключения часов реального времени. Особое внимание уделяется обмену данными микроэлектронных устройств с персональным компьютером, приводятся примеры программ.

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

Описание книги Assembler. Программирование на языке ассемблера IBM PC :
Один из лучших учебников по ассемблеру. Книга имеет малый объем. Очень хорошо изложены многие важнейшие темы — сегментация, прерывания, двоичная арифметика. Подробно рассмотрены команды ЭВМ, конструкции языка и методы программирования на нем. Изложение сопровождается многочисленными примерами. Как "краткий курс" пожалуй не имеет себе равных.

Книга представляет собой учебное пособие по языку ассемблер для персональных компьютеров типа IBM PC. Подробно рассмотрены команды этих ЭВМ, конструкция языка и методы программирования на нем. Изложение сопровождается многочисленными примерами. Для студентов и преподавателей ВУЗов, для всех желающих детально изучить язык ассемблер и приемы программирования на нем.

Описание книги Программирование на языке Ассемблера для микроконтроллеров семейства i8051 :
Изложены основы программирования на языке Ассемблера для популярного семейства микроконтроллеров i8051. Описаны особенности архитектуры микроконтроллеров семейства i8051. Приведены сведения о технологии разработки программ, системе и форматах команд. Книга содержит информацию о программировании некоторых типов задач, в том числе задач цифровой фильтрации сигналов, а также несколько рекомендаций о стиле программирования для начинающих программистов.

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

Описание книги Ассемблер для процессоров Intel Pentium :
Издание посвящено вопросам программирования на языке ассемблера для процессоров Intel Pentium. Рассмотрен широкий круг вопросов, начиная с основ программирования на ассемблере и заканчивая применением самых современных технологий обработки данных, таких как MMS, SSE и SSE2. Материал книги раскрывает методику оптимизации программного кода для всех поколений процессоров Intel Pentium, включая Intel Pentium 4. Теоретический материал подкреплен многочисленными примерами программного кода. Для широкого круга читателей, от студентов до опытных разработчиков программного обеспечения.

Вовсе необязательно быть хакером, чтобы писать программы на языке ассемблера. Бесспорно лишь одно – добиться полного контроля над различными компонентами компьютера без знания языка ассемблера вряд ли возможно. В настоящей книге автор в доступной форме знакомит читателей со всеми основными конструкциями языка ассемблера, демонстрируя на конкретных примерах варианты решения различных практических задач. Можно предположить, что именно подбор задач отличает различные издания по данной тематике. Ведь набор операций по сути неизменен. Особенность настоящей книги заключается в удачном сочетании формального стиля изложения с доступными для понимания примерами. С другой стороны, значительная часть материала представляет интерес для читателей, уже имеющих опыт работы с этим языком. Это касается особенностей новых архитектур процессоров, обеспечения интерфейса с языками высокого уровня и командам расширений современных процессоров. Эти темы практически не освещены в литературе, что значительно увеличивает интерес к данной книге. Содержание книги и доступный характер изложения материала позволяют рекомендовать ее для самой широкой аудитории читателей, не только решивших самостоятельно изучить язык ассемблера, но и почерпнуть дополнительную информацию по более тонким вопросам системного программирования.

Описание книги Изучаем Ассемблер :
Книга посвящена основам программирования на Ассемблере в системах Windows и DOS. Знание Ассемблера необходимо профессиональному программисту для понимания работы операционной системы и компилятора. Ассемблер позволяет написать программу (или ее часть) так, что она будет быстро выполняться и при этом занимать мало места. Это любимый язык хакеров; его знание позволяет менять по своему усмотрению программы, имея только исполнимый файл без исходных текстов. В основу изложения положены короткие примеры на ассемблере MASM фирмы Microsoft, вводящие читателя в круг основных идей языка, знание которых позволяет не только писать простые программы, но и самостоятельно двигаться дальше.

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

Описание книги Использование ассемблера для оптимизации программ на C++ :
Рассматривается использование языка ассемблера для оптимизации программ, написанных на языке C++. Подробно изложены вопросы применения современных технологий обработки данных ММХ и SSE, а также использования особенностей архитектур современных процессоров для оптимизации программ. Приведены практические рекомендации по оптимизации логических структур высокого уровня, использованию эффективных алгоритмов вычислений, работе со строками и массивами данных.

В книгу включены примеры программного кода приложений, иллюстрирующие различные аспекты применения ассемблера. В качестве средств разработки примеров используются макроассемблер MASM 6.14 и Microsoft Visual C++ .NET 2003. Исходные тексты программ содержатся на прилагаемом к книге компакт-диске.

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

Пример из другой области

Если брать в качестве примера бокс, то все подобные руководства учат исполнять удар, перемещаться стоя на полу, но абсолютно отсуствует то, что делает бокс - боксом, а не «разрешенным мордобитием». То есть комбинационная работа, особенности использования ринга, защитные действия, тактическое построение боя и, тем более, стратегия боя не рассматриваются вообще. Научили человека бить по «груше» и сразу на ринг. Это в корне неверно. Но именно так построены практически все «учебники» и «руководства» по программированию на ассемблере.


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

Идилия?

Маленькое замечание, далее по тексту будет использована классификация, отличающаяся от распространненной в настоящее время. Однако это не является поводом для «споров о цвете истины», просто в данном виде проще объяснить точку зрения автора на программирование.

Итак, на сегодняшний день, казалось бы, для программистов наступила эпоха счастья. Огромный выбор средств на все случаи жизни и пожелания. Тут тебе и миллионы «фреймворков»/«паттернов»/«шаблонов»/«библиотек» и тысячи средств «облегчающих» программирование, сотни языков и диалектов, десятки методологий и различные подходы у программированию. Бери – нехочу. Но не «берется». И дело не в религиозных убеждениях, а в том, что это все выглядит как попытка питаться чем-то невкусным. При желании и усердии можно приноровиться и к этому, конечно. Но, возвращаясь к программированию, в большинстве из предлагаемого не видно технической красоты – видно лишь множество «костылей». Как результат, при использовании этих «достижения», из-под «кисти художников» вместо завораживающих пейзажей выходит сплошная «абстракция», или лубки - если повезет. Неужели большинство программистов такие бездари, неучи и имеют проблемы на уровне генетики? Нет, не думаю. Так в чем же причина?
На сегодняшний день имеется множество идей и способов программирования. Рассмотрим наиболее «модные» из них.

  • Императивное программирование – в данном подходе программист задает последовательность действий, приводящих к решению задачи. В основе лежит разделение программы на части, выполняющие логически независимые операции (модули, функции, процедуры). Но в отличии от типизированного подхода (см. ниже) тут есть важная особенность – отсутствие «типизации» переменных. Иными словами отсутствует понятие «тип переменной», вместо него используется понимание, что значения у одной и той же переменной могут иметь различный тип. Яркими представителем данного подхода являются Basic, REXX, MUMPS.
  • Типизированное программирование – модификация императивного программирования, когда программист и система ограничивают возможные значения переменных. Из наиболее известных языков - это Pascal, C.
  • Функциональное программирование – это более математический способ решения задачи, когда решение состоит в «конструировании» иерархии функций (и соответственно создание отсутствующих из них), приводящей к решению задачи. Как примеры: Lisp, Forth.
  • Автоматное программирование – подход, где программист строит модель/сеть, состоящую из обменивающихся сообщениями объектов/исполнительных элементов, как изменяющих/хранящих свое внутреннее «состояние» так и могущих взаимодействовать с внешним миром. Иными словами это то, что обычно называют «объектное программирование» (не объектно-ориентированное). Этот способ программирования представлен в Smalltalk.
А как-же множество других языков? Как правило, это уже «мутанты». Например, смешение типизированного и автоматного подхода дало «объектно-ориентированное программирование».

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

Казалось бы, программируя на ассемблере, мы вольны делать все и так, что и как пожелаем и позволяет нам «железо». Но как только нам захочется использовать «универсальный драйвер» для какого-либо типа оборудования, мы вынуждены менять свободу «творчества» на предписанные (стандартизированные) подходы и способы использования драйвера. Как только нам понадобилась возможность использовать наработки других коллег или дать им возможность делать тоже самое с плодами нашего труда - мы вынуждены менять свободу выбора взаимодействия между частями программы на некие обговоренные/стандартизированные способы.

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

Чем дальше в лес, тем толще

Итак, мы осознали, что хотя ассемблер очень прост, но пользоваться им надо уметь. И основная слажность - это необходимость взаимодействия со средой исполнения, где запускается наша программа. Если программисты на языках высокого уровня уже имеют доступ к необходимым библиотекам, функциям, подпрограммам на многие случаи жизни и им доступны способы взаимодействия с внешним миром, в виде, согласованном с идеей языка, то программисту на ассемблере приходится продираться сквозь чащу всевозможных препонов, водруженных на пустом месте. Когда смотришь на то, что генерируют языки высокого уровня при компиляции, то складывает ощущение, что, те, кто писал компиляторы, либо понятия не имеют, как работает процессор с архитектурой x86, «или одно из двух» (ц).

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

Еще один пример из другой области

Как яркий пример системного подхода можно привести производство грузовиков в США. В данном случае, производитель грузовика – это просто изготовитель рамы и кабины + сборщик конструктора. Все остальное (двигатель, трансмиссия, подвеска, электрооборудование и т.д.) берется исходя из пожеланий заказчика. Захотел один заказчик получиться себе некий Kenworth с двигателем от Detroit Diesel, ручной коробкой Fuller, рессорной подвеской от какой-нибудь Dana – пожалуйста. Понадобилась другу этого заказчика та же модель Kenworth, но с «родным» двигателем Paccar, коробкой-автоматом Allison и пневмоподвеской от другого производителя – легко! И так делают все сборщики грузовиков в США. То есть грузовик – это система, в котором каждый модуль может быть заменен на другой, того же назначения и безпроблемно состыкован с уже имеющимися. Причем способ стыковки модулей сделан с максимально доступной универсальностью и удобством дальнейшего расширения функционала. Вот к чему должен стремиться инженер.

К сожалению, нам придется жить с тем, что есть, но в дальнейшем подобного следует избегать. Итак, программа – это, по сути, набор модулей (невожно как они называются, и как себя «ведут»), компонуя которые мы добиваемся решения стоящей задачи. Для эффективности крайне желательно, чтобы можно было эти модули использовать повторно. Причем не просто использовать любой ценой, а использовать удобным способом. И вот тут нас ждет очередной неприятный «сюрприз». Большинство языков высокого уровня оперируют такими структурными единицами как «фунция» и «процедура». И, как способ взяимодействия с ними, применяется «передача параметров». Это вполне логично, и тут никаких вопросов не возникает. Но как всегда, «важно не то, что делается – важно как делается» (ц). И вот тут начинается самое непонятное. На сегодня распространены 3 способа организации передачи параметров: cdecl , stdcall , fastcall . Так вот, ни один из этих способов не является «родным» для x86. Более того, все они ущербны с точки зрения расширения функционала вызываемых подпрограмм. То есть, увеличив количество передаваемых параметров, мы вынуждены менять все точки вызова этой функции/подпрограммы, или же плодить новую подпрограмму с похожим функционалом, которая будет вызываться немного иным способом.

Указанные выше методы передачи параметров относительно неплохо работают на процессорах с 2мя раздельными стеками (стеком данных, и стеком адресов/управления) и развитыми командами манипулирования стеком (хотя бы индексное обращение к элементам стека). Но при программировании на x86 приходится сначала извращаться при передаче/получении параметров, а потом не забыть «структурное» их удаление из стека. Попутно стараясь угадать/рассчитать максимальную глубину стека. Напомним, что x86 (16/32 битный режим), это процессор, у которого:

  • специализированные регистры (РОНы – регистры общего назначения – как таковые отсутствуют: то есть, мы не можем одной командой умножить содержимое регистра GS на значение из EDI и результат получить в паре EDX:ECX, или же разделить значение из пары регистров EDI:ESI на содержимое регистра EAX);
  • регистров мало;
  • один стек;
  • ячейка памяти не дает никакой информации от типа хранящегося там значения.
Иначе говоря, методы программирования, используемые для процессоров с большим регистровым файлом, с поддержкой нескольких независимых стеков и т.д. в большинстве своем не применимы при программировании на x86.

Следующая особенность взамиодействия с готовыми модулями, написанными на «языках высокого уровня»- это «борьба» с «типами переменных». С одной стороны, причина появления типов переменных ясна – программист знает какие значения используются внутри его подпрограммы / модуля. Исходя из этого, видится вполне логичным, что, задав тип значений переменной, мы можем «упростить» написание программы, возложив контроль типов/пределов значений на транслятор языка. Но и тут с водой выплеснули младенца. Потому как любая программа пишется не для генерации сферических коней в вакууме, а для практической работы с пользовательскими данными. То есть очевидное нарушение системного подхода – как будто разработчики языков высокого уровня рассматривали свои системы без учета взаимодействия с внешним миром. В итоге, программируя на типизированном языке разработчик должен предсматривать все возможные виды «неправильных» входных данных, и искать способы обхода неопределенностей. И вот тут на сцену выходят монструозные системы поддержки регулярных выражений, обработки исключительных ситуаций, сигнатуры методов/процедур для разных типов значений и прочая прочая генерация костылей.

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

Эта причина (извращенная передача параметров в модули, написанные на языках высого уровня и необходимость строго следить за типами передаваемых параметров в теже самые модули) видится основной, из-за которой программирование на ассемблере неоправданно затруднено. И большинство предпочитает разбираться в дебрях «языков высокого уровня», чтобы воспользоваться тем, что уже наработано другими, чем мучиться, вставляю одни и те же «типовые» костыли, для исправления того, чего они не делали. И редкий транслятор ассемблера хоть как-то «разгружает» программиста от этой рутины.

Что делать?

Предварительные выводы с учетом 15ти летного перерыва в программировании на ассемблере.
Во-первых, по поводу модулей или частей программы. В общем случае стоит выделить два вида исполнительных модулей программы на языке ассемблера – «операция» и «подпрограмма».
  • «Операцией» будем называть модуль, выполняющий «атомарное» действие и не требующий для своего выполнения множества параметров (например, операция очистки всего экрана, или операция расчета медианы числового ряда и т.п.).
  • «Подпрограммой» же стоит назвать фунциональный модуль, требующий, для корректного функционирования, множество входных параметров (больше 2х-3х).
И тут стоит оценить опыт императивных и функциональных языков. Они нам подарили 2 ценных инструмента, которыми стоит воспользоваться: «структура данных» (или, на примере REXX – составные/дополняемые переменные) и «немутабельность данных».

Полезно также следовать правилу немутабельности – то есть неизменности передаваемых параметров. Подпрограмма не может (не должна) менять значения в передаваемой ей структуре и результат возврашает либо в регистрах (не более 2х-3х параметров), либо также в новой, создаваемой структуре. Таким образом мы избавлены от необходимости делать копии структур, на случай «забытого» изменения данных подпрограммами, и можем использовать уже созданную структуру целиком или основную ее часть для вызова нескольких подпрограмм, оперирующих одним/схожим набором параметров. Более того, практически «автоматом» приходим к очередному «функциональному» правилу – внутренней контексто-независимости подпрограмм и операций. Иными словами - к разделению состояния/данных от метода/подпрограммы их обработки (в отличие от автоматной модели). В случаях параллельного программирования, а также совместного использования одной подпрограммы мы избавляемся как от необходимости плодить множество контекстов исполнения и следить за их «непересечением», так и от создания множества экземляров одной подпрограмм с разными «состояниями», в случае нескольких ее вызовов.

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

Это были общие соображения о программировании на ассемблере, не касающиеся вопросов проектирования, отладки и обработки ошибок. Надеюсь что разработчикам ОС, которые пишут их с 0-ля (а тем более на ассемблере), будет о чем подумать и они выберут (пусть не описанные выше, а любые иные) способы сделать программирование на ассемблере более систематизированным, удобным и приятным, а не будут слепо копировать чужие, зачастую безнадежно «кривые» варианты.