Программирование на языке ассемблера берман
Программирование на языке ассемблера берман
Подборка книг по Assembler
1. Калашников О.
«Ассемблер – это просто. Учимся программировать» (2011)
Настоящее раздолье для новичков, которые еще гуглят базовую терминологию и ищут ассемблер учебник. Это он и есть. Помимо ознакомления с языком и первых программ, также затронуты болевые точки – прерывания: штука несложная, но поначалу тяжелая для восприятия. С каждой главой ассемблер уроки усложняются, и на выходе читатель сможет писать программы на ассемблере, оптимизировать их, работать с вирусами, антивирусами, памятью и файловыми системами.
2. Аблязов Р.
«Программирование на ассемблере на платформе x86-64» (2011)
Акцент делается на работе процессора в защищенном режиме и long mode. Это незаменимая база для программирования в Win32 и Win64, которая затрагивает команды ассемблера, прерывания, механизмы трансляции и защиты с учетом режимных отличий. Рассматривается разработка оконных приложений и драйверов. Данный ассемблер учебник подойдет начинающим кодерам и тем, кто сразу перешел к программированию на ассемблере, но плохо разобрался в аппаратной платформе x86-64.
3. А.В. Столяров
«Программирование на языке ассемблера NASM для ОС Unix» (2011)
Пособие основано на лекциях, читавшихся автором в рамках курса «Архитектура ЭВМ и язык ассемблера» в Ташкентском филиале МГУ весной 2007 года; часть материала также прошла апробацию в рамках курса «Архитектура ЭВМ и системное программное обеспечение» кафедры Прикладной математики МГТУГА в 2008, 2009 и 2010 гг
4. Джефф Дантеманн
«Assembly Language Step–by–Step: Programming with Linux»
Вы узнаете:
— об основных понятиях, лежащих в основе вычислений и программирования, включая шестнадцатеричные и двоичные числа;
— об эволюции процессоров Intel и то, как работают современные процессоры x86;
— начать программировать (редактирование исходного кода с помощью сборки, компоновки и отладки);
— что такое Comprehend x86 32-разрядная адресная память с защищенным режимом;
— изучите набор инструкций x86, разделив множество полных примеров программ;
— научитесь работать с бесплатными программными утилитами под Ubuntu Linux, включая редактор Kate, ассемблер NASM и набор инструментов GNU;
— об ссновных практических возможностях программирования Linux, включая процедуры, макросы, шлюз вызова INT 80h и вызовы в стандартные
библиотеки C.
5. Рудольф Марек «Ассемблер на примерах»
6. Белов А. В. «Микроконтроллеры AVR. От азов программирования до создания практических устройств» (2016)
За шесть шагов новый практический самоучитель позволит пройти путь от “чайника” изучающего азы цифровой техники, до вполне готового
специалиста, умеющего самостоятельно разрабатывать схемы любых устройств на микроконтроллерах и составлять для них программы. Изучив базовые элементы цифровой логики, читатель осваивает основы микропроцессорной техники, возможности семейства микроконтроллеров AVR, основы схемотехники и конструирования микроэлектронных устройств. Читатель изучит сразу два языка программирования для микроконтроллеров (язык Ассемблера и язык СИ), учится транслировать, отлаживать программы, прошивать их в память микроконтроллера, самостоятельно разрабатывать собственные микроконтроллерные устройства. Книга предназначена для широкого круга читателей: начинающих специалистов по электронной технике, студентов технических ВУЗов и всех желающих изучить эту область человеческих знаний.
Погружение в assembler. Полный курс по программированию на асме от ][
Содержание статьи
Это первая (вступительная) статья курса. Курс рассчитан на тех, кто в целом знаком с высокоуровневым программированием и только приступает к изучению ассемблера.
Читай далее:
Но что такое программирование само по себе по своей сути, вне зависимости от какого-либо языка? Разнообразие ответов поражает. Наиболее часто можно услышать такое определение: программирование — это составление инструкций или команд для последовательного исполнения их машиной с целью решить ту или иную задачу. Такой ответ вполне справедлив, но, на мой взгляд, не отражает всей полноты, как если бы мы назвали литературу составлением из слов предложений для последовательного прочтения их читателем. Я склонен полагать, что программирование ближе к творчеству, к искусству. Как любой вид искусства — выражение творческой мысли, идеи, программирование представляет собой отражение человеческой мысли. Мысль же бывает и гениальная, и совершенно посредственная.
Но, каким бы видом программирования мы ни занимались, успех зависит от практических навыков вкупе со знанием фундаментальных основ и теории. Теория и практика, изучение и труд — вот краеугольные камни, на которых основывается успех.
В последнее время ассемблер незаслуженно находится в тени других языков. Обусловлено это глобальной коммерциализацией, направленной на то, чтобы в максимально короткие сроки получить как можно большую прибыль от продукта. Иными словами, массовость взяла верх над элитарностью. А ассемблер, по моему мнению, ближе к последнему. Гораздо выгоднее в сравнительно небольшие сроки поднатаскать ученика в таких, например, языках, как С++, С#, 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 выглядит так:
В синтаксисе же AT&T уже будет иной вид:
В среде ОС 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). Они описываются в спецификации процессоров.
Например, процессор Zilog Z80 наследовал систему команд Intel i8080, расширил ее и поменял некоторые команды (и обозначения регистров) на свой лад. Например, сменил Intel-команду mov на ld. Процессоры Motorola Fireball наследовали систему команд Z80, несколько ее урезав. Вместе с тем Motorola официально вернулась к Intel-командам, и в данный момент половина ассемблеров для Fireball работает с Intel-командами, а половина — с командами Zilog.
Директивы
Кроме ассемблерных команд, программа может содержать директивы — команды, не переводящиеся непосредственно в машинные инструкции, а управляющие работой компилятора. Набор и синтаксис их значительно разнятся и зависят не от аппаратной платформы, а от используемого компилятора. В качестве набора директив можно выделить:
Достоинства и недостатки
К достоинствам можно отнести следующее:
За недостатки можно принять:
Почему следует изучать язык ассемблера?
В современной практике индустриального программирования языки ассемблера применяются крайне редко. Для разработки низкоуровневых программ практически в большинстве случаев используется язык си, позволяющий достигать тех же целей многократно меньшими затратами труда, причем с такой же, а иногда и большей эффективностью получаемого исполняемого кода (последнее достигается за счет применения оптимизаторов). На ассемблере сейчас реализуются очень специфические участки ядер операционных систем и системных библиотек. Более того, программирование на ассемблере было вытеснено и из такой традиционно ассемблерной области, как программирование микроконтроллеров. Большей частью прошивки для них также пишут на си. Тем не менее программирование на языке ассемблера очень часто применяется при написании программ, использующих возможности процессора, не реализуемые языками высокого уровня, а также при программировании всевозможных нестандартных программистских хитростей. Отдельные ассемблерные модули, как и ассемблерные вставки в текст на других языках, присутствуют и в ядрах операционных систем, и в системных библиотеках того же языка си и других языков высокого уровня. Сегодня едва ли кому придет в голову сумасшедшая мысль писать крупную программу на чистом ассемблере.
Так зачем же тратить время на его изучение? По ряду веских причин, и вот одна из них: ассемблер — это краеугольный камень, на котором покоится все бесконечное пространство программирования, начиная от рождения первого процессора. Каждый физик мечтает разгадать тайну строения вселенной, найти эти загадочные первичные неделимые (низкоуровневые) элементы, из которых она состоит, не удовлетворяясь лишь смутным о том представлением квантовой теории. Ассемблер же и есть та первичная материя, из которой состоит вселенная процессора. Он — тот инструмент, который дает человеку способность мыслить в терминах машинных команд. А подобное умение просто необходимо любому профессиональному программисту, даже если никогда в жизни он не напишет ни единой ассемблерной строчки. Нельзя отрицать того, что невозможно стать математиком, совершенно не имея понятия об элементарной арифметике. На каком бы языке вы ни писали программы, необходимо хотя бы в общих чертах понимать, что конкретно будет делать процессор, исполняя ваше высочайшее повеление. Если такого понимания нет, программист начинает бездумно применять все доступные операции, совершенно не ведая, что на самом деле он творит.
Вообще, профессиональный пользователь компьютера, системный ли администратор, или программист, может позволить себе что-то не знать, но ни в коем случае не может позволить не понимать сути происходящего, как устроена вычислительная система на всех ее уровнях, от электронных логических схем до громоздких прикладных программ. А непонимание чего-то влечет за собой ощущение в глубине подсознания некоей загадочности, непостижимого таинства, происходящего по мановению чьей-то волшебной палочки. Такое ощущение для профессионала недопустимо категорически. Он просто обязан быть уверен вплоть до глубинных слоев подсознания, что то устройство, с которым он имеет дело, ничего волшебного и непознаваемого собой не представляет.
Иными словами, до тех пор пока существуют процессоры, ассемблер будет необходим.
В этом отношении совершенно не важно, какую конкретно архитектуру и язык какого конкретного ассемблера изучать. Зная один язык ассемблера, ты с успехом можешь начать писать на любом другом, потратив лишь некоторое время на изучение справочной информации. Но самое главное в том, что, умея мыслить языком процессора, ты всегда будешь знать, что, для чего, почему и зачем происходит. А это уже не просто уровень программирования мышкой, а путь к созданию программного обеспечения, несущего печать великого мастерства.
Ассемблер — программирование или искусство?
Скажем так, все зависит от того, в чьих руках он находится. Ассемблер — это первичный элемент мира процессора, из сочетаний этих элементов складывается его душа, его самосознание. Подобно тому, как вся музыка, написанная в истории человечества, состоит из сочетаний семи нот, так и сочетание ассемблерных команд наполняет компьютерный мир цифровой жизнью. Кто знает лишь три аккорда — это «попса», кому же известна вся палитра — это классика.
Почему же наука так жаждет проникнуть в квантовые глубины и захватить в свои руки неуловимый первичный кирпичик материи? Чтобы получить над ней власть, изменять ее по своей воле, стать на уровень Творца Вселенной. В чьи руки попадет такая власть — это еще вопрос. В отличие от науки, в мире программирования тайн нет, нам известны кирпичики, его составляющие, а следовательно, и та власть над процессором, которую нам дает знание ассемблера.
Чтобы программирование на языке ассемблера поднялось на уровень искусства, нужно постичь его красоту, скрывающуюся за потоком единиц и нулей. Как и в любой отрасли человеческой деятельности, в программировании можно быть посредственностью, а можно стать Мастером. И то и другое отличает степень культуры, образования, труда и, главное, то, сколько души автор вкладывает в свое творение.
Ассемблер и терминатор
Не так давно Джеймс Кэмерон выпустил в свет 3D-версию второго «Терминатора», и в качестве интересного исторического факта можно отметить один любопытный момент из жизни киборга-убийцы.
Кадр из фильма «Терминатор»
Здесь мы видим «зрение» терминатора, а слева на нем отображается ассемблерный листинг. Судя по нему, знаменитый Уничтожитель работал на процессоре MOS Technology 6502 либо на MOS Technology 6510. Этот процессор впервые был разработан в 1975 году, использовался на компьютерах Apple и, помимо всего прочего, на знаменитых игровых приставках того времени Atari 2600 и Nintendo Entertainment System (у нас более известной как Dendy). Имел лишь три 8-разрядных регистра: А-аккумулятор и два индексных регистра X и Y. Такое малое их количество компенсировалось тем, что первые 256 байт оперативной памяти (так называемая нулевая страница) могли адресоваться специальным образом и фактически использовались в качестве 8-разрядных или 16-разрядных регистров. У данного процессора было 13 режимов адресации на всего 53 команды. У терминатора идет цепочка инструкций LDA-STA-LDA-STA. В семействе 6502 программы состояли чуть менее чем полностью из LDA/LDY/LDX/STA/STX/STY:
Чтение и запись в порты ввода-вывода также выполнялись этими командами, и программа терминатора имеет вполне осмысленный вид, а не представляет собой бестолковую фантазию сценариста: MOS Technology 6502 / Система команд.
Отрасли практического применения
Ранее упоминалось, что в наше время ассемблер почти вытеснен языками высокого уровня. Однако и по сей день ему находится применение. Приведем некоторые примеры.
Вместо заключения
Мы продолжим погружаться в ассемблер в следующих статьях цикла. Темы этого цикла мы в целом определили, но если у тебя есть какие-нибудь идеи или пожелания — смело пиши в комменты, все учтем. 🙂
HackWare.ru
Этичный хакинг и тестирование на проникновение, информационная безопасность
Введение в Ассемблер
Оглавление
Данный материал — это азы языка программирования Ассемблер для абсолютных новичков. Здесь говорится о том, как написать программу на Ассемблере, приводятся основные команды Ассемблера, имеются примеры программа на Ассемблер и подробно описано как скомпилировать первую программу.
Если у вас есть опыт изучения или даже программирования на других языках, всё равно Ассемблер потребует понимания новых концепций.
Руководство по программированию на Ассемблер
Язык Ассемблер — это низкоуровневый язык программирования для компьютеров или других программируемых устройств, он специфичен для конкретной компьютерной архитектуры центрального процессора, что отличает его от большинства высокоуровневых языков программирования, которые обычно портативны среди разных систем. Язык Ассемблер преобразуется в исполняемый машинный код с помощью служебной программы, называемой ассемблером, такой как NASM, MASM и т. д.
Для кого эти уроки по ассемблеру
Этот учебник был разработан для тех, кто хочет изучить основы программирования на Ассемблере с нуля. Из этих уроков вы получите достаточное представление о программировании на Ассемблере, благодаря которому вы сможете продолжить обучения в данной области и подняться на высокий уровень знаний.
Что нужно для изучения Ассемблера
Прежде чем приступить к этому учебному пособию, вы должны иметь базовые знания по терминологии компьютерного программирования. Базовое понимание любого из языков программирования поможет вам понять концепции программирования на Ассемблере и быстро продвигаться в процессе обучения.
Что такое язык Ассемблер?
Каждый персональный компьютер имеет микропроцессор, который управляет арифметической, логической и контрольной активностью.
Каждая семья процессоров имеет свой собственный набор инструкций для обработки различных операций, таких как получения ввода с клавиатуры, отображение информации на экране и выполнения различных других работ. Этот набор инструкций называется «инструкции машинного языка» (‘machine language instructions’).
Процессор понимает только инструкции машинного языка, которые являются строками из единиц и нулей. При этом машинный язык слишком непонятный и сложный для использования его в разработки программного обеспечения. И низкоуровневый язык Ассемблер предназначен для определённый групп процессоров, он представляет различные инструкции в символическом коде и более понятной форме.
Преимущества языка Ассемблер
Знание языка ассемблера позволяет понять:
Другие преимущества использования ассемблера:
Системы счисления
Основные характеристики аппаратной составляющей ПК
Каждый компьютер содержит процессор и оперативную память. Процессор содержит регистры — компоненты, которые содержат данные и адреса. Для выполнения программы, система копирует её с устройства постоянного хранения во внутреннюю память. Процессор выполняет инструкции программы.
Фундаментальной единицей компьютерного хранилища является бит. Он может быть в состоянии Включён (1) или Выключен (0). Группа из восьми связанных битов составляет байт, из которых семь бит используются для данных, а ещё один используется для контроля чётности. Согласно правилу чётности, количество битов, которые Включены (1) в каждом байте, всегда должно быть чётным. То есть бит чётности имеет значение 1, если у соответствующего байта количество 1-х битов нечётно. 0 — если иначе (чётно).
Таким образом, бит чётности используется для того, чтобы сделать количество битов в байте чётным. Если соотношение является нечётным, система предполагает, что произошла ошибка соотношения (хотя и редко), которая могла быть вызвана неисправностью оборудования или электрическими помехами.
Выше бит чётности рассмотрен на примере «even parity», то есть «чётная чётность». Также существует «odd parity», то есть «нечётная чётность». В первом случае подгоняется под чётное количество единиц как было показано выше. А во втором случае подгоняется под нечётное количество единиц.
Двоичная система счисления
В каждой системе счисления используются позиционные обозначения, то есть каждая позиция, в которой записана цифра, имеет различное позиционное значение. Каждая позиция — это степень базы, которая равна 2 для двоичной системы счисления, и эти степени начинаются с 0 и увеличиваются на 1.
В следующей таблице приведены позиционные значения для 8-битного двоичного числа, где все биты установлены в положение ON (Включено).
Значение бита | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
Значение позиции как степень основания 2 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
Номер бита | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Значение двоичного числа, как и в десятичном, зависит от составляющих его цифр и расположения этих цифр. Но в двоичном числе используются только цифры 1 и 0, и расположение цифр имеет другое значение степени. Первая цифра, как и в десятичном числе, может означать 0 или 1. Вторая цифра (смотрим число справа на лево) может означать 2 (если этот бит установлен на 1) или 0 (если бит установлен на 0). Третья цифра (смотрим число справа на лево) может означать 4 (если этот бит установлен на 1) или 0 (если бит установлен на 0). И так далее. В десятичном числе значение каждого символа нужно умножить на 10 в степени порядкового номера этой цифры за минусом единицы.
То есть число 1337 это 1 * 10 3 + 3 * 10 2 + 3 * 10 1 + 7 * 10 0 = 1337
В двоичной системе всё точно также, только вместо десятки в степени порядкового номера за минусом единицы, нужно использовать двойку — вот и всё!
Допустим число 110101 и мы хотим узнать, сколько это будет в десятичной системе счисления, для этого достаточно выполнить следующее преобразование:
1 * 2 5 * + 1 * 2 4 + 0 * 2 3 + 1 * 2 2 + 0 * 2 1 + 1 * 2 0 = 1 * 32 + 1 * 16 + 0 * 8 + 1 * 4 + 0 * 2 + 1 * 1 = 53
Итак, значение бинарного числа основывается на наличии битов 1 и их позиционном значении. Поэтому значение числа 11111111 в двоичной системе является:
1 + 2 + 4 + 8 +16 + 32 + 64 + 128 = 255
Шестнадцатеричная система счисления
Шестнадцатеричная система счисления использует основание 16. Цифры в этой системе варьируются от 0 до 15. По соглашению, буквы от A до F используются для представления шестнадцатеричных цифр, соответствующих десятичным значениям с 10 по 15.
Десятичное число | Двоичный вид | Шестнадцатеричный вид |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
2 | 10 | 2 |
3 | 11 | 3 |
4 | 100 | 4 |
5 | 101 | 5 |
6 | 110 | 6 |
7 | 111 | 7 |
8 | 1000 | 8 |
9 | 1001 | 9 |
10 | 1010 | A |
11 | 1011 | B |
12 | 1100 | C |
13 | 1101 | D |
14 | 1110 | E |
15 | 1111 | F |
Чтобы преобразовать двоичное число в его шестнадцатеричный эквивалент, разбейте его на группы по 4 последовательные группы в каждой, начиная справа, и запишите эти группы в соответствующие цифры шестнадцатеричного числа.
Чтобы преобразовать шестнадцатеричное число в двоичное, просто запишите каждую шестнадцатеричную цифру в её 4-значный двоичный эквивалент.
Отрицательные двоичные числа
Компьютерные процессы действуют по своей логике и своим алгоритмам. И привычные нам операции вычитания, деления, умножения выполняются необычным для нас, но удобным для микропроцессора способом.
Удобством для арифметических действий в процессоре обусловлено то, как записываются отрицательные двоичные числа. Вы должны помнить из курса информатики, что в одном байте содержится 8 бит. Но старший бит используется для установки знака. Чтобы правильно прочесть число, а также правильно поменять его знак, нужно выполнять следующие правила:
Во-первых, нужно помнить, что если старшие биты (крайние слева), равны нулю, то их иногда не записывают. Например, восьмибитное число 10 (в десятичной системе счисления оно равно 2), также можно записать как 0000 0010. Обе эти записи означают число 2.
Если старший бит равен нулю, то это положительное число. Например, возьмём число 110. В десятичной системе счисления это 6. Данное число является положительным или отрицательным? На самом деле, однозначно на этот вопрос можно ответить только зная разрядность числа. Если это восьмиразрядное число, то его полная запись будет такой: 0000 0110. Как можно увидеть, старший бит равен нулю, следовательно, это положительное число.
Для трёхбитовых чисел было бы справедливо следующее:
Десятичное значение | |
---|---|
0 | 000 |
1 | 001 |
2 | 010 |
3 | 011 |
-4 | 100 |
-3 | 101 |
-2 | 110 |
-1 | 111 |
Как вы должны были понять после анализа предыдущей таблицы, для смены знака недостаточно просто поменять единицу на ноль — для преобразования числа в отрицательное, а также для чтения отрицательного числа существуют особые правила.
Отрицательные двоичные числа записываются без знака минус и для получения этого же числа со знаком минус (то есть для получения числа в Дополненном коде) нужно выполнить два действия:
На русском языке такая форма записи называется Дополнительный код, в англоязычной литературе это называется Two’s complement.
Примеры восьмибитного двоичного числа в Дополнительном коде (старший бит указывает на знак):
Десятичное значение | Двоичное значение трёхбитового числа со знаком (в представлении Дополнительный код) |
---|---|
0 | 0000 0000 |
1 | 0000 0001 |
2 | 0000 0010 |
126 | 0111 1110 |
127 | 0111 1111 |
−128 | 1000 0000 |
−127 | 1000 0001 |
−126 | 1000 0010 |
−2 | 1111 1110 |
−1 | 1111 1111 |
Десятичное представление | |
---|---|
127 | 0111 1111 |
1 | 0000 0001 |
0 | 0000 0000 |
-0 | — |
-1 | 1111 1111 |
-2 | 1111 1110 |
-3 | 1111 1101 |
-4 | 1111 1100 |
-5 | 1111 1011 |
-6 | 1111 1010 |
-7 | 1111 1001 |
-8 | 1111 1000 |
-9 | 1111 0111 |
-10 | 1111 0110 |
-11 | 1111 0101 |
-127 | 1000 0001 |
-128 | 1000 0000 |
Числа в дополненном коде удобно применять для вычитания — это будет показано далее.
Для преобразования отрицательного числа, записанного в дополнительном коде, в положительное число, записанное в прямом коде, используется похожий алгоритм.
Добавив к результату 1 получим положительное число 5 в прямом коде:
И проверим, сложив с дополнительным кодом
0000 0101 + 1111 1011 = 1 0000 0000, десятый разряд выбрасывается, то есть получается 0000 0000, то есть 0. Следовательно, преобразование выполнено правильно, так как 5 + (-5) = 0.
Двоичная арифметика
Следующая таблица иллюстрирует четыре простых правила для двоичного сложения:
(i) | (ii) | (iii) | (iv) |
---|---|---|---|
1 | |||
0 | 1 | 1 | 1 |
+0 | +0 | +1 | +1 |
=0 | =1 | =10 | =11 |
Эту таблицу нужно читать по столбцам сверху вниз. В первом столбце складываются 0 и 0 — в результате получается 0. Во втором примере складываются 1 и 0 (или 0 и 1 — без разницы), в результате получается 1. В третьем столбце складываются две единицы — в результате в текущей позиции получается 0, но на одну позицию влево добавляется единица. Если в этой позиции уже есть единица — то применяется это же правило, то есть в позиции пишется 0, и 1 передаётся влево. В четвёртом примере складываются три единицы — в результате, в текущей позиции записывается 1, и ещё одна 1 передаётся влево.
Десятичные | Двоичные |
---|---|
60 | 00111100 |
+42 | 00101010 |
102 | 01100110 |
Рассмотрим, как делается вычитание.
Для вычитания число, которое вычитается, записывается в форме Дополнительного кода, а затем эти два числа складываются.
Пример: Вычесть 42 из 53
Бит который вызывает переполнение — крайней левый, девятый по счёту, просто отбрасывается.
Адресация данных в памяти
Процессор может одновременно обращаться к одному или нескольким байтам памяти. Давайте рассмотрим шестнадцатеричное число 0725H (буква H означает, что перед нами шестнадцатеричное число). Для этого числа потребуется два байта памяти. Байт старшего разряда или старший значащий байт — 07, а младший байт — 25.
Процессор хранит данные в последовательности обратного байта, то есть байт младшего разряда хранится в низком адресе памяти и байт старшего разряда в старшем адресе памяти. Таким образом, если процессор переносит значение 0725H из регистра в память, он сначала перенесёт 25 на нижний адрес памяти и 07 на следующий адрес памяти.
Когда процессор получает числовые данные из памяти для регистрации, он снова переворачивает байты. Есть два вида адресов памяти:
Настройка рабочего окружения для Ассемблер
Настройка локального рабочего окружения
Язык ассемблера зависит от набора команд и архитектуры процессора. В этом руководстве мы сосредоточимся на процессорах Intel-32, таких как Pentium. Чтобы следовать этому уроку, вам понадобится:
Есть много хороших ассемблерных программ, таких как:
Мы будем использовать ассемблер NASM, так как он:
Установка NASM
Если вы выбираете «Инструменты разработки» при установке Linux, вы можете установить NASM вместе с операционной системой Linux, и вам не нужно загружать и устанавливать его отдельно. Чтобы проверить, установлен ли у вас NASM, сделайте следующее:
Откройте терминал Linux.
и нажмите клавишу ВВОД.
Если он уже установлен, появляется строка типа
В противном случае вы увидите просто
значит вам нужно установить NASM.
NASM должен присутствовать в стандартных репозиториях, поэтому попробуйте поискать там этот пакет и установить его.
Например, для установки в Debian, Ubuntu, Linux Mint, Kali Linux и их производные выполните:
Для установки в Arch Linux, BlackArch и их производные выполните:
Чтобы установить NASM из исходного кода, сделайте следующее:
Проверьте веб-сайт ассемблера (NASM) на последнюю версию.
Распакуйте архив в каталог, который создаст подкаталог nasm-X.XX.
Перейдите к nasm-X.XX
Этот скрипт оболочки найдёт лучший компилятор C для использования и сделает настройки в соответствии с Makefiles.
чтобы создать двоичные файлы nasm и ndisasm.
чтобы установить nasm и ndisasm в /usr/local/bin и установить справочные страницы (man).
Это должно установить NASM в вашей системе. Кроме того, вы можете использовать RPM-дистрибутив для Fedora Linux. Эта версия проще в установке, просто дважды щёлкните файл RPM.
Основы синтаксиса Ассемблера
Программу на языке Ассемблер можно разделить на три раздела:
Раздел data
Раздел data используется для объявления инициализированных данных или констант. Эти данные не изменяются во время выполнения. В этом разделе вы можете объявить различные постоянные значения, имена файлов или размер буфера и т. д.
Синтаксис объявления раздела data:
Раздел BSS
Секция bss используется для объявления переменных. Синтаксис объявления раздела bss:
Раздел text
Раздел text используется для хранения самого кода. Этот раздел должен начинаться с объявления global _start, которое сообщает ядру, где начинается выполнение программы.
Синтаксис объявления раздела text:
Комментарии
Комментарий на ассемблере начинается с точки с запятой (;). Он может содержать любой печатный символ, включая пробел. Он может появиться в строке сам по себе, например:
или в той же строке вместе с инструкцией, например:
Операторы Ассемблера
Программы на ассемблере состоят из трёх типов операторов:
Исполняемые инструкции или просто инструкции говорят процессору, что делать. Каждая инструкция состоит из кода операции (opcode). Каждая исполняемая инструкция генерирует одну инструкцию на машинном языке.
Директивы ассемблера или псевдооперации говорят ассемблеру о различных аспектах процесса сборки. Они не являются исполняемыми и не генерируют инструкции машинного языка.
Макросы — это в основном механизм подстановки текста.
Синтаксис операторов ассемблера
Операторы языка ассемблера вводятся по одной инструкции в каждой строке. Каждое утверждение имеет следующий формат:
Поля в квадратных скобках являются необязательными. Основная инструкция состоит из двух частей: первая — это имя инструкции (или мнемоника), которая должна быть выполнена, а вторая — операнды или параметры команды.
Ниже приведены некоторые примеры типичных операторов языка ассемблера.
Программа Hello World на Ассамблере
Следующий код на ассемблере выводит на экран строку «Hello World»:
Когда приведённый выше код скомпилирован и выполнен, он даст следующий результат:
Компиляция и связывание (Linking) программы на Ассемблере в NASM
Убедитесь, что вы установили путь до исполнимых файлов nasm и ld в вашей переменной окружения PATH (в Linux это уже сделано). Теперь пройдите следующие шаги для компиляции и связывания приведённой выше программы:
Выполните программу набрав:
Если вы всё сделали правильно, то она отобразит на экране ‘Hello, world!’.
Возможно, вас интересует, что такое связывание (Linking) и зачем оно требуется после сборки программы. Если коротко, то на этом этапе объектные файлы (если их несколько) собираются в один исполнимый файл, также благодаря этому процессу исполнимый файл теперь может использовать библиотеки. Линкеру указывается (обычно) целевой исполнимый формат. Если совсем коротко — это просто нужно. Я не буду в этом базовом курсе по ассемблеру останавливаться на этом более подробно — если вас интересует эта тема, то вы всегда сможете найти по ней дополнительную информацию в Интернете.
Ассемблер: сегменты памяти
Мы уже рассмотрели три раздела программы на ассемблере. Эти разделы также представляют различные сегменты памяти.
Обратите внимание, что если вы замените ключевое слово section на слово segment, вы получите тот же самый результат. Попробуйте этот код:
После компиляции и выполнения вышеприведённого кода он даст следующий результат:
Сегменты памяти
Модель сегментированной памяти делит системную память на группы независимых сегментов, на которые ссылаются указатели, расположенные в регистрах сегментов. Каждый сегмент используется для хранения данных определённого типа. Один сегмент используется для хранения кодов команд, другой — для хранения элементов данных, а третий — для программного стека.
В свете вышеизложенного мы можем выделить различные сегменты памяти, такие как:
Ассемблер: регистры (Registers)
Операции процессора в основном связаны с обработкой данных. Эти данные могут быть сохранены в памяти и доступны оттуда. Однако чтение данных из памяти и её сохранение в памяти замедляет процессор, поскольку включает сложные процессы отправки запроса данных через шину управления и в блок хранения памяти и получения данных по одному и тому же каналу.
Для ускорения работы процессора процессор включает в себя несколько мест хранения внутренней памяти, называемых регистрами (registers).
Регистры хранят элементы данных для обработки без необходимости доступа к памяти. Ограниченное количество регистров встроено в чип процессора.
Регистры процессора
В архитектуре IA-32 имеется десять 32-разрядных и шесть 16-разрядных процессорных регистров. Регистры сгруппированы в три категории:
Общие регистры далее делятся на следующие группы:
Регистры данных
Четыре 32-битных регистра данных используются для арифметических, логических и других операций. Эти 32-битные регистры можно использовать тремя способами:
Некоторые из этих регистров данных имеют конкретное применение в арифметических операциях.
AX — основной аккумулятор; он используется во вводе/выводе и большинстве арифметических инструкций. Например, в операции умножения один операнд сохраняется в регистре EAX или AX или AL в соответствии с размером операнда.
BX известен как базовый регистр, поскольку его можно использовать при индексированной адресации.
CX известен как регистр подсчёта, так как регистры ECX, CX хранят счётчик циклов в итерационных операциях.
DX известен как регистр данных. Он также используется в операциях ввода/вывода. Он также используется с регистром AX вместе с DX для операций умножения и деления с большими значениями.
Регистры указателя
Регистры указателя являются 32-разрядными регистрами EIP, ESP и EBP и соответствующими 16-разрядными правыми частями IP, SP и BP. Есть три категории регистров указателей:
Индексные регистры
32-разрядные индексные регистры ESI и EDI и их 16-разрядные крайние правые части. SI и DI, используются для индексированной адресации и иногда используются для сложения и вычитания. Есть два набора указателей индекса:
Регистры управления
Регистр указателя 32-битной инструкции и регистр 32-битных флагов рассматриваются как регистры управления.
Многие инструкции включают сравнения и математические вычисления и изменяют состояние флагов, а некоторые другие условные инструкции проверяют значение этих флагов состояния, чтобы перенести поток управления в другое место.
Популярные биты флага:
В следующей таблице указано положение битов флага в 16-битном регистре флагов:
Флаг: | O | D | I | T | S | Z | A | P | C | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Номер бита: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Сегментные регистры
Сегменты — это специальные области, определённые в программе для хранения данных, кода и стека. Есть три основных сегмента:
Помимо регистров DS, CS и SS существуют и другие регистры дополнительных сегментов — ES (дополнительный сегмент), FS и GS, которые предоставляют дополнительные сегменты для хранения данных.
При программировании на ассемблере программе необходим доступ к ячейкам памяти. Все области памяти в сегменте относятся к начальному адресу сегмента. Сегмент начинается с адреса, равномерно делимого на 16 или в шестнадцатеричном виде числа 10. Таким образом, крайняя правая шестнадцатеричная цифра во всех таких адресах памяти равна 0, что обычно не сохраняется в регистрах сегментов.
Сегментные регистры хранят начальные адреса сегмента. Чтобы получить точное местоположение данных или инструкции в сегменте, требуется значение смещения (или смещение). Чтобы сослаться на любую ячейку памяти в сегменте, процессор объединяет адрес сегмента в регистре сегмента со значением смещения местоположения.
Пример
Посмотрите на следующую простую программу, чтобы понять использование регистров в программировании на Ассемблере. Эта программа отображает 9 звёзд на экране вместе с простым сообщением.
После компиляции и выполнения эта программа выведет:
Ассемблер: Системные вызовы
Системные вызовы — это API для интерфейса между пространством пользователя и пространством ядра. Мы уже использовали системные вызовы sys_write и sys_exit для записи на экран и выхода из программы соответственно.
Системные вызовы Linux
Вы можете использовать системные вызовы Linux в ваших ассемблерных программах. Для использования системных вызовов Linux в вашей программе необходимо выполнить следующие шаги:
Существует шесть регистров, в которых хранятся аргументы используемого системного вызова. Это EBX, ECX, EDX, ESI, EDI и EBP. Эти регистры принимают последовательные аргументы, начиная с регистра EBX. Если существует более шести аргументов, ячейка памяти первого аргумента сохраняется в регистре EBX.
В следующем фрагменте кода показано использование системного вызова sys_exit:
В следующем фрагменте кода показано использование системного вызова sys_write:
Все системные вызовы перечислены в /usr/include/asm/unistd.h вместе с их номерами (значение, которое нужно указать в EAX перед вызовом int 80h). Точнее говоря, сейчас это файлы /usr/include/asm/unistd_32.h и /usr/include/asm/unistd_64.h.
Чтобы посмотреть содержимое файла /usr/include/asm/unistd_32.h:
Начало этого файла:
Чтобы получить справку по системным вызовам:
Чтобы получить справку по конкретному вызову, укажите вначале man 2, а затем название вызова. Например, чтобы узнать о вызове read:
Чтобы узнать о вызове mkdir:
В следующей таблице приведены некоторые системные вызовы, используемые в этом руководстве:
%eax | Имя | %ebx | %ecx | %edx | %esx | %edi |
---|---|---|---|---|---|---|
1 | sys_exit | int (целое число) | — | — | — | — |
2 | sys_fork | struct pt_regs | — | — | — | — |
3 | sys_read | unsigned int (целое беззнаковое число) | char * | size_t | — | — |
4 | sys_write | unsigned int (целое беззнаковое число) | const char * | size_t | — | — |
5 | sys_open | const char * | int (целое число) | int (целое число) | — | — |
6 | sys_close | unsigned int (целое беззнаковое число) | — | — | — | — |
Пример
Следующий пример читает число с клавиатуры и отображает его на экране:
Скомпилированный и запущенный вышеприведённый код даёт следующий результат:
Ассемблер: Режимы адресации
Большинство инструкций на ассемблере требуют обработки операндов. Адрес операнда предоставляет место, где хранятся данные, подлежащие обработке. Некоторые инструкции не требуют операнда, в то время как некоторые другие инструкции могут требовать один, два или три операнда.
Когда инструкции требуется два операнда, первый операнд обычно является пунктом назначения, который содержит данные в регистре или ячейке памяти, а второй операнд является источником. Источник содержит либо данные для доставки (немедленная адресация), либо адрес (в регистре или памяти) данных. Как правило, исходные данные остаются неизменными после операции.
Три основных режима адресации:
Адресации на регистр
В этом режиме адресации регистр содержит операнд. В зависимости от инструкции регистр может быть первым операндом, вторым операндом или обоими.
Поскольку обработка данных между регистрами не требует памяти, она обеспечивает самую быструю обработку данных.
Немедленная адресация
Непосредственный операнд имеет постоянное значение или выражение. Когда инструкция с двумя операндами использует немедленную адресацию, первый операнд может быть регистром или ячейкой памяти, а второй операнд является непосредственной константой. Первый операнд определяет длину данных.
Адресация на память
Когда операнды указываются в режиме адресации на память, требуется прямой доступ к основной памяти, обычно к сегменту данных. Этот способ адресации приводит к более медленной обработке данных. Чтобы найти точное местоположение данных в памяти, нам нужен начальный адрес сегмента, который обычно находится в регистре DS, и значение смещения. Это значение смещения также называется действующим адресом (effective address).
В режиме прямой адресации значение смещения указывается непосредственно как часть инструкции, обычно указывается именем переменной. Ассемблер вычисляет значение смещения и поддерживает таблицу символов, в которой хранятся значения смещения всех переменных, используемых в программе.
При прямой адресации в памяти один из операндов ссылается на ячейку памяти, а другой операнд ссылается на регистр.
Прямая адресация со смещением
Этот режим адресации использует арифметические операторы для изменения адреса. Например, посмотрите на следующие определения, которые определяют таблицы данных:
Следующие операции обращаются к данным из таблиц в памяти в регистрах:
Косвенная адресация на память
В этом режиме адресации используется способность компьютера Segment:Offset (Сегмент:Смещение). Обычно для этой цели используются базовые регистры EBX, EBP (или BX, BP) и регистры индекса (DI, SI), закодированные в квадратных скобках для ссылок на память.
Косвенная адресация обычно используется для переменных, содержащих несколько элементов, таких как массивы. Начальный адрес массива хранится, скажем, в регистре EBX.
В следующем фрагменте кода показано, как получить доступ к различным элементам переменной.
Инструкция MOV
Мы уже задействовали инструкцию MOV, которая используется для перемещения данных из одного пространства хранения в другое. Инструкция MOV принимает два операнда.
Синтаксис
Синтаксис инструкции MOV:
Инструкция MOV может иметь одну из следующих пяти форм:
Пожалуйста, обратите внимание, что:
Инструкция MOV порой вызывает двусмысленность. Например, посмотрите на утверждения:
Не ясно, хотите ли вы переместить байтовый эквивалент или словесный эквивалент числа 110. В таких случаях целесообразно использовать спецификатор типа (type specifier).
В следующей таблице приведены некоторые общие спецификаторы типов:
Спецификатор типа | Байты |
---|---|
BYTE | 1 |
WORD | 2 |
DWORD | 4 |
QWORD | 8 |
TBYTE | 10 |
Пример
Следующая программа иллюстрирует некоторые из концепций, обсуждённых выше. Он сохраняет имя «Zara Ali» в разделе данных памяти, затем программно меняет его значение на другое имя «Nuha Ali» и отображает оба имени.
Когда приведённый выше код скомпилирован и выполнен, он даёт следующий результат:
Ассемблер: Переменные
NASM предоставляет различные директивы определения (define directives) для резервирования места для хранения переменных. Директива определения ассемблера используется для выделения пространства хранения. Его можно использовать для резервирования, а также для инициализации одного или нескольких байтов.
Выделение пространства хранения для инициализированных данных
Синтаксис для оператора распределения памяти для инициализированных данных:
Где имя-переменной — это идентификатор для каждого пространства хранения. Ассемблер связывает значение смещения для каждого имени переменной, определённого в сегменте данных.
Существует пять основных форм директивы определения:
Директива | Цель | Размер хранения |
---|---|---|
DB | Определить Byte | выделяет 1 байт |
DW | Определить Word | выделяет 2 байта |
DD | Определить Doubleword | выделяет 4 байта |
DQ | Определить Quadword | выделяет 8 байта |
DT | Определить Ten Bytes | выделяет 10 байта |
Ниже приведены некоторые примеры использования директив определения.
Пожалуйста, обратите внимание, что:
Следующая программа показывает использование директивы определения:
Когда приведённый выше код компилируется и выполняется, он даёт следующий результат:
Выделение дискового пространства для неинициализированных данных
Директивы резервирования используются для запаса места для неинициализированных данных. Директивы резервирования принимают один операнд, который определяет количество единиц пространства, которое будет зарезервировано. Каждая директива определения имеет связанную директиву резервирования.
Существует пять основных форм директив резервирования:
Директива | Цель |
---|---|
RESB | Зарезервировать Byte |
RESW | Зарезервировать Word |
RESD | Зарезервировать Doubleword |
RESQ | Зарезервировать Quadword |
REST | Зарезервировать 10 байт |
Множественность определений
Вы можете иметь несколько операторов определения данных в программе. Например:
Ассемблер выделяет непрерывную память для нескольких определений переменных.
Множественность инициализаций
Директива TIMES позволяет выполнить несколько инициализаций к одному и тому же значению. Например, массив с именем marks размера 9 может быть определён и инициализирован на начальное значение ноль с помощью следующего оператора:
Директива TIMES полезна при определении массивов и таблиц. Следующая программа отображает 9 звёздочек на экране:
Результат выполнения скомпилированной программы:
Ассемблер: Константы
NASM предоставляет несколько директив, определяющих константы. Мы уже использовали директиву EQU в предыдущих разделах. Особое внимание мы уделим трём директивам:
Директива EQU
Директива EQU используется для определения констант. Синтаксис директивы EQU следующий:
Затем вы можете использовать это постоянное значение в вашем коде, например:
Операндом оператора EQU может быть выражение:
Приведённый фрагмент кода определит AREA как 200.
Пример
Следующий пример иллюстрирует использование директивы EQU:
Скомпилированный и выполненный код даст следующие результаты:
Кстати, в коде программы мы использовали 0xA,0xD в качестве части строк. Точнее говоря, в качестве окончания строк. Как можно догадаться, это шестнадцатеричные цифры. При выводе на экран эти шестнадцатеричные цифры трактуются как коды символов ASCII. То есть, чтобы понять их значение, нужно заглянуть в таблицу ASCII символов, например в статье «ASCII и шестнадцатеричное представление строк. Побитовые операции со строками».
Там мы можем найти, что 0xA (в той таблице он обозначен как 0A) и означает он перевод строки. Во многих языках программирования символ обозначается как «\n». Нажатие на клавишу ↵ Enter при выводе текста переводит строку.
Что касается 0xD (там в таблице он обозначен как 0D) и означает enter / carriage return — возврат каретки. Во многих языках программирования — символ «CR» обозначается как «\r».
Итак, если вы программируете на каком либо языке, то последовательность из двух шестнадцатеричных чисел 0xA,0xD, соответствует последовательности «\n\r», то есть, упрощённо говоря, это универсальный способ (чтобы срабатывал и в Linux, и в Windows) перейти на новую строку.
Директива %assign
По аналогии с директивой EQU, директива %assign может использоваться для определения числовых констант. Эта директива допускает переопределение. Например, вы можете определить постоянную TOTAL следующим образом:
Позже в коде вы можете переопределить её так:
Эта директива чувствительна к регистру.
Директива %define
Директива %define позволяет определять как числовые, так и строковые константы. Эта директива похожа на #define в C. Например, вы можете определить постоянную PTR так:
Приведённый выше код заменяет PTR на [EBP+4].
Эта директива также допускает переопределение и учитывает регистр.
Ассемблер: Арифметические инструкции
Инструкция INC
Инструкция INC используется для увеличения операнда на единицу. Она работает с одним операндом, который может находиться либо в регистре, либо в памяти.
Инструкция INC имеет следующий синтаксис:
Операндом может быть 8-битный, 16-битный или 32-битный операнд.
Инструкция DEC
Инструкция DEC используется для уменьшения операнда на единицу. Она работает с одним операндом, который может находиться либо в регистре, либо в памяти.
Инструкция DEC имеет следующий синтаксис:
Операндом может быть 8-битный, 16-битный или 32-битный операнд.
Инструкции ADD и SUB
Команды ADD и SUB используются для выполнения простого сложения/вычитания двоичных данных размером в byte, word и doubleword, т.е. для сложения или вычитания 8-битных, 16-битных или 32-битных операндов соответственно.
Инструкции ADD и SUB имеют следующий синтаксис:
Инструкция ADD/SUB может выполняться между:
Однако, как и другие инструкции, операции с память-в-память невозможны с использованием инструкций ADD/SUB. Операция ADD или SUB устанавливает или очищает флаги переполнения (overflow) и переноса (carry).
Пример
В следующем примере программа спросит у пользователя две цифры; сохранит их в регистрах EAX и EBX, соответственно; сложит эти значения; сохранит результат в ячейке памяти «res» и, наконец, отобразит результат.
Скомпилированный и выполненный код даст следующие результаты:
Код программы сильно упрощается, если прописать значения переменных для арифметических действий прямо в самом коде:
Результат выполнения этого кода:
Инструкции MUL/IMUL
Есть две инструкции для умножения двоичных данных. Инструкция MUL (Multiply) обрабатывает беззнаковые данные, а IMUL (Integer Multiply) обрабатывает данные со знаком. Обе инструкции влияют на флаг переноса и переполнения.
Синтаксис для инструкций MUL/IMUL следующий:
Множимое в обоих случаях будет в аккумуляторе, в зависимости от размера множимоего и умножителя, и результат умножения также сохраняется в двух регистрах в зависимости от размера операндов. Следующий раздел объясняет инструкции MUL в трёх разных случаях:
Номер | Сценарии | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 |
Номер | Сценарии | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 |
Номер | Инструкция | Формат |
---|---|---|
1 | AND | AND операнд1, операнд2 |
2 | OR | OR операнд1, операнд2 |
3 | XOR | XOR операнд1, операнд2 |
4 | TEST | TEST операнд1, операнд2 |
5 | NOT | NOT операнд1 |
Первый операнд во всех случаях может быть либо в регистре, либо в памяти. Второй операнд может быть либо в регистре/памяти, либо в непосредственном (постоянном) значении. Однако операции память-и-память невозможны. Эти инструкции сравнивают или сопоставляют биты операндов и устанавливают флаги CF, OF, PF, SF и ZF.
Инструкция AND (И)
Инструкция AND используется для поддержки логических выражений путём выполнения побитовой операции AND. Побитовая операция AND возвращает 1, если совпадающие биты обоих операндов равны 1, в противном случае она возвращает 0. Например:
Операция AND может использоваться для очистки одного или нескольких битов. Например, допустим, регистр BL содержит 0011 1010. Если вам нужно очистить старшие биты до нуля, то вы выполняете операцию AND этого регистра с 0FH.
Давайте рассмотрим другой пример. Если вы хотите проверить, является ли данное число нечётным или чётным, простой тест будет проверять младший значащий бит числа. Если это 1, число нечётное, иначе число чётное.
Предполагая, что номер находится в регистре AL, мы можем написать:
Следующая программа иллюстрирует это.
Результат выполнения кода:
Измените значение в регистре ax на нечётную цифру, к примеру:
Программа будет отображать:
Точно так же очистить весь регистр вы можете сделав AND с 00H.
Инструкция OR
Инструкция OR (ИЛИ) используется для выполнения логической побитовой операции OR. Побитовый оператор OR возвращает 1, если совпадающие биты одного или обоих операндов равны единице. Возвращает 0, если оба бита равны нулю.
Операция OR может использоваться для установки одного или нескольких битов. Например, предположим, что регистр AL содержит 0011 1010, вам нужно установить на единицы четыре младших бита, тогда вы можете сделать OR со значением 0000 1111, т.е.
В следующем примере демонстрируется инструкция OR. Давайте сохраним значения 5 и 3 в регистрах AL и BL, соответственно, затем
затем в регистре AL в результате выполнения операции OR получится 7
Результат работы программы:
Инструкция XOR
Инструкция XOR реализует побитовую операцию XOR. Операция XOR устанавливает результирующий бит в 1, если и только если биты из операндов отличаются. Если биты из операндов одинаковы (оба 0 или оба 1), результирующий бит сбрасывается в 0.
XOR операнд числа с самим собой меняет операнд на 0. Это используется для очистки регистра.
Инструкция TEST
Инструкция TEST работает так же, как и операция AND, но в отличие от инструкции AND она не меняет первый операнд. Таким образом, если нам нужно проверить, является ли число в регистре чётным или нечётным, мы также можем сделать это, используя инструкцию TEST, не меняя исходного числа.
Инструкция NOT
Инструкция NOT реализует побитовую операцию NOT. Операция NOT меняет биты в операнде на противоположные. Операнд может быть либо в регистре, либо в памяти.
Ассемблер: Условия
Выполнение в зависимости от выполнения условия на ассемблере реализовано несколькими инструкциями зацикливания и ветвления. Эти инструкции могут изменить поток управления в программе. Условное исполнение рассматривается в двух сценариях:
№ | Инструкции условия | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 |
Инструкция | Описание | Тестируемые флаги |
---|---|---|
JE/JZ | Jump Equal or Jump Zero (равно или ноль) | ZF |
JNE/JNZ | Jump not Equal or Jump Not Zero (не равно или не ноль) | ZF |
JG/JNLE | Jump Greater or Jump Not Less/Equal (больше или не меньше/равно) | OF, SF, ZF |
JGE/JNL | Jump Greater/Equal or Jump Not Less (больше/равно или не меньше) | OF, SF |
JL/JNGE | Jump Less or Jump Not Greater/Equal (меньше или не больше/равно) | OF, SF |
JLE/JNG | Jump Less/Equal or Jump Not Greater (меньше/равно или не больше) | OF, SF, ZF |
Ниже приведены инструкции условного перехода, используемые для данных без знака, используемых для логических операций.
Инструкция | Описание | Тестируемые флаги |
---|---|---|
JE/JZ | Jump Equal или Jump Zero (равно или ноль) | ZF |
JNE/JNZ | Jump not Equal или Jump Not Zero (не равно или не ноль) | ZF |
JA/JNBE | Jump Above или Jump Not Below/Equal (больше или не меньше/равно) | CF, ZF |
JAE/JNB | Jump Above/Equal или Jump Not Below (больше/равно или не меньше) | CF |
JB/JNAE | Jump Below или Jump Not Above/Equal (меньше или не больше/равно) | CF |
JBE/JNA | Jump Below/Equal или Jump Not Above (меньше/равно или не больше) | AF, CF |
Следующие инструкции условного перехода имеют специальное использование и проверяют значение флагов:
Инструкция | Описание | Тестируемый флаг |
---|---|---|
JXCZ | Переход если CX равен нулю | нет |
JC | Переход если Перенос | CF |
JNC | Переход если нет Переноса | CF |
JO | Переход если переполнение | OF |
JNO | Переход если нет переполнения | OF |
JP/JPE | Переход при наличии чётности | PF |
JNP/JPO | Переход при отсутствии чётности | PF |
JS | Переход при наличии знака (отрицательная величина) | SF |
JNS | Переход при отсутствии знака (положительная величина) | SF |
Синтаксис для набора инструкций J :
Пример
Следующая программа отображает наибольшую из трёх переменных. Переменные являются двузначными переменными. Три переменные num1, num2 и num3 имеют значения 47, 22 и 31 соответственно:
Результат работы программы:
Ассемблер: Петли
Инструкция JMP может использоваться для реализации циклов. Например, следующий фрагмент кода может использоваться для выполнения тела цикла 10 раз.
Набор инструкций процессора, однако, включает в себя группу команд цикла для реализации итерации. Основная инструкция LOOP имеет следующий синтаксис:
Где label — метка цели, которая идентифицирует целевую инструкцию, как в инструкциях перехода. Инструкция LOOP предполагает, что регистр ECX содержит количество циклов. Когда инструкция цикла выполняется, регистр ECX уменьшается, и управление переходит к метке назначения, пока значение регистра ECX, то есть счётчик не достигнет нуля.
Приведённый выше фрагмент кода может быть записан как:
Пример
Следующая программа печатает цифры от 1 до 9 на экране:
Когда приведённый выше код скомпилирован и выполнен, он даёт следующий результат:
Ассемблер: Числа
Числовые данные обычно представлены в двоичной системе. Арифметические инструкции работают с двоичными данными. Когда числа отображаются на экране или вводятся с клавиатуры, они имеют форму ASCII (смотрите также ASCII и шестнадцатеричное представление строк. Побитовые операции со строками).
До сих пор мы преобразовывали эти входные данные в форме ASCII в двоичные для арифметических вычислений и преобразовывали результат обратно в ASCII. Следующий код показывает это:
После компиляции и выполнения приведённый выше код даёт следующий результат:
Однако такие преобразования имеют накладные расходы, и программирование на ассемблере позволяет более эффективно обрабатывать числа в двоичной форме. Десятичные числа могут быть представлены в двух формах:
ASCII представление
В представлении ASCII десятичные числа хранятся в виде строки символов ASCII. Например, десятичное значение 1234 сохраняется как:
Где 31H — это значение ASCII для 1, 32H — это значение ASCII для 2 и т. д. Есть четыре инструкции для обработки чисел в представлении ASCII:
Эти инструкции не принимают никаких операндов и предполагают, что требуемый операнд находится в регистре AL.
В следующем примере инструкция AAS используется для демонстрации концепции:
После компиляции и выполнения приведённый выше код даёт следующий результат:
BCD представление
Существует два типа представления BCD:
В неупакованном представлении BCD каждый байт хранит двоичный эквивалент десятичной цифры. Например, число 1234 хранится как:
Есть две инструкции для обработки этих чисел:
Четыре инструкции настройки ASCII, AAA, AAS, AAM и AAD, также могут использоваться с неупакованным представлением BCD. В упакованном представлении BCD каждая цифра сохраняется с использованием четырёх битов. Две десятичные цифры упакованы в байт. Например, число 1234 хранится как:
Есть две инструкции для обработки этих чисел:
В упакованном представлении BCD отсутствует поддержка умножения и деления.
Пример
Следующая программа складывает два пятизначных десятичных числа и отображает сумму. Он использует вышеуказанные концепции:
Результат после компиляции и выполнения:
Ассемблер: Строки
Мы уже использовали строки переменной длины в наших предыдущих примерах. Строки переменной длины могут содержать столько символов, сколько необходимо. Как правило, мы указываем длину строки одним из двух способов:
Мы можем хранить длину строки явно, используя символ счётчика местоположения $, который представляет текущее значение счётчика расположения. В следующем примере:
В качестве альтернативы, вы можете хранить строки с последующим символом стража, чтобы разделить строку, вместо того, чтобы явно хранить длину строки. Страдный символ должен быть специальным символом, который не появляется в строке.
Строковые инструкции
Каждая строковая инструкция может требовать исходного операнда, целевого операнда или обоих. Для 32-битных сегментов строковые инструкции используют регистры ESI и EDI для указания на операнды источника и назначения соответственно.
Однако для 16-битных сегментов регистры SI и DI используются для указания на источник и пункт назначения соответственно.
Существует пять основных инструкций для обработки строк, а именно:
Каждая из вышеприведённых инструкций имеет версию байта, слова и двойного слова, а строковые инструкции могут повторяться с использованием префикса повторения.
В этих инструкциях используются пары регистров ES:DI и DS:SI, где регистры DI и SI содержат действительные адреса смещения, которые относятся к байтам, хранящимся в памяти. SI обычно ассоциируется с DS (сегмент данных), а DI всегда ассоциируется с ES (дополнительный сегмент).
Регистры DS:SI (или ESI) и ES:DI (или EDI) указывают на операнды источника и назначения соответственно. Предполагается, что в памяти операндом-источником является DS:SI (или ESI), а операндом-адресатом — ES:DI (или EDI).
Для 16-битных адресов используются регистры SI и DI, а для 32-битных адресов используются регистры ESI и EDI.
В следующей таблице представлены различные версии строковых инструкций и предполагаемое пространство операндов.
Основная инструкция | Операнды в | Операция с байтом | Операция с Word | Операция с Double |
---|---|---|---|---|
MOVS | ES:DI, DS:SI | MOVSB | MOVSW | MOVSD |
LODS | AX, DS:SI | LODSB | LODSW | LODSD |
STOS | ES:DI, AX | STOSB | STOSW | STOSD |
CMPS | DS:SI, ES: DI | CMPSB | CMPSW | CMPSD |
SCAS | ES:DI, AX | SCASB | SCASW | SCASD |
Префиксы повторения
Префикс REP, если он установлен перед строковой инструкцией, например — REP MOVSB, вызывает повторение инструкции на основе счётчика, размещённого в регистре CX. REP выполняет инструкцию, уменьшает CX на 1 и проверяет, равен ли CX нулю. Он повторяет обработку инструкций, пока CX не станет равным нулю.
Флаг направления (DF) определяет направление операции.
Префикс REP также имеет следующие варианты:
Ассемблер: Массивы
Мы уже обсуждали, что директивы определения данных к ассемблеру используются для выделения памяти переменным. Переменная также может быть инициализирована с определенным значением. Инициализированное значение может быть указано в шестнадцатеричной, десятичной или двоичной форме.
Например, мы можем определить переменную word ‘months’ любым из следующих способов:
Директивы определения данных также могут использоваться для определения одномерного массива. Определим одномерный массив чисел.
Вышеприведённое определение объявляет массив из шести слов (word), каждое из которых инициализируется числами 34, 45, 56, 67, 75, 89. Это выделяет 2×6 = 12 байтов последовательного пространства памяти. Символический адрес первого числа будет NUMBERS, а второго номера — NUMBERS + 2 и т. д.
Давайте рассмотрим другой пример. Вы можете определить массив с именем inventory размером 8 и инициализировать все значения с нуля следующим образом:
Который может быть сокращён до:
Директива TIMES также может использоваться для нескольких инициализаций одного и того же значения. Используя TIMES, массив INVENTORY можно определить как:
Пример
В следующем примере демонстрируются вышеуказанные концепции, определяя массив из трёх элементов x, в котором хранятся три значения: 2, 3 и 4. Программа добавляет значения в массив и отображает сумму 9:
Результат после компиляции и выполнения приведённого выше кода:
Ассемблер: Процедуры
Процедуры или подпрограммы очень важны для ассемблера, так как программы на ассемблере, как правило, имеют большой размер. Процедуры идентифицируются по имени. После её названия описывается тело процедуры, которая выполняет чётко определённую работу. Конец процедуры указывается оператором ret (return, возврат).
Ниже приведён синтаксис для определения процедуры:
Процедура вызывается из другой функции с помощью инструкции CALL. Инструкция CALL должна иметь имя вызываемой процедуры в качестве аргумента, как показано ниже:
Вызываемая процедура возвращает управление вызывающей процедуре с помощью инструкции RET.
Пример
Давайте напишем очень простую процедуру с именем sum, которая складывает переменные, хранящиеся в регистре ECX и EDX, и возвращает сумму в регистр EAX:
Скомпилированный и выполненный код даст следующий результат:
Структура данных стеков
Стек представляет собой массив данных в виде массива в памяти, в котором данные могут храниться и удаляться из места, называемого «вершиной» стека. Данные, которые необходимо сохранить, «помещаются» в стек, а извлекаемые данные «выталкиваются» из стека. Стек — это структура данных LIFO, то есть данные, сохранённые первыми, извлекаются последними.
Язык ассемблера предоставляет две инструкции для операций со стеком: PUSH и POP. Эти инструкции имеют следующий синтаксис:
Пространство памяти, зарезервированное в сегменте стека, используется для реализации стека. Регистры SS и ESP (или SP) используются для реализации стека. На вершину стека, которая указывает на последний элемент данных, вставленный в стек, указывает регистр SS:ESP, где регистр SS указывает на начало сегмента стека, а SP (или ESP) даёт смещение в сегмент стека.
Реализация стека имеет следующие характеристики:
Для хранения данных регистров в стеке, он может использоваться следующим образом:
Пример
Следующая программа отображает весь набор символов ASCII. Основная программа вызывает процедуру с именем display, которая отображает набор символов ASCII.
Приведённый выше код после компиляции и выполнения даст следующий результат:
Ассемблер: Рекурсия
Рекурсивная процедура — это та, которая вызывает сама себя. Существует два вида рекурсии: прямая и косвенная. При прямой рекурсии процедура вызывает себя, а при косвенной рекурсии первая процедура вызывает вторую процедуру, которая, в свою очередь, вызывает первую процедуру.
Рекурсию можно наблюдать в многочисленных математических алгоритмах. Например, рассмотрим случай вычисления факториала числа. Факториал числа задаётся уравнением:
Например: факториал 5 равен 1 x 2 x 3 x 4 x 5 = 5 x факториал 4, и это может быть хорошим примером демонстрации рекурсивной процедуры. Каждый рекурсивный алгоритм должен иметь конечное условие, то есть рекурсивный вызов программы должен быть остановлен при выполнении условия. В случае алгоритма факториала конечное условие достигается, когда n равно 0.
Следующая программа показывает, как факториал числа n реализован на ассемблере. Для простоты программы мы вычислим факториал 3.
Скомпилированный и выполненный вышеприведённый код даст следующий результат:
Ассемблер: Макросы
Написание макроса — это ещё один способ обеспечения модульного программирования на ассемблере.
Макрос — это последовательность инструкций, которой присвоено имя, и которая может использоваться в любом месте программы.
В NASM макросы определяются с помощью директив %macro и %endmacro.
Макрос начинается с директивы %macro и заканчивается директивой %endmacro.
Синтаксис для определения макроса:
Макрос вызывается с использованием имени макроса вместе с необходимыми параметрами. Когда вам нужно многократно использовать некоторую последовательность инструкций в программе, вы можете поместить эти инструкции в макрос и использовать их вместо того, чтобы писать инструкции постоянно.
Например, очень распространённая потребность в программах заключается в написании строки символов на экране. Для отображения строки символов вам понадобится следующая последовательность инструкций:
В приведённом выше примере отображения строки символов регистры EAX, EBX, ECX и EDX были использованы вызовом функции INT 80H. Таким образом, каждый раз, когда вам нужно отобразить что-то на экране, вам нужно сохранить эти регистры в стеке, вызвать INT 80H, а затем восстановить исходное значение регистров из стека. Таким образом, было бы полезно написать два макроса для сохранения и восстановления данных.
Мы заметили, что некоторые инструкции, такие как IMUL, IDIV, INT и т. д нуждаются в том, чтобы некоторая информация была сохранена в некоторых конкретных регистрах и даже возвращала значения в некоторых конкретных регистрах. Если программа уже использовала эти регистры для хранения важных данных, то существующие данные из этих регистров должны быть сохранены в стеке и восстановлены после выполнения инструкции.
Пример
Следующий пример показывает создание и использование макросов:
Выполнение этой программы даст следующий результат:
Ассемблер: Управление файлами
Система рассматривает любые входные или выходные данные как поток байтов. Есть три стандартных файловых потока:
Файловый дескриптор
Файловый дескриптор — это 16-разрядное целое число, назначаемое файлу в качестве идентификатора файла. Когда создаётся новый файл или открывается существующий файл, дескриптор файла используется для доступа к файлу.
Файловый дескриптор стандартных файловых потоков — stdin, stdout и stderr — равны 0, 1 и 2 соответственно.
Файловый указатель
Файловый указатель определяет местоположение для последующей операции чтения/записи в файле в виде байтов. Каждый файл рассматривается как последовательность байтов. Каждый открытый файл связан с указателем файла, который задаёт смещение в байтах относительно начала файла. Когда файл открыт, указатель файла устанавливается в ноль.
Системные вызовы обработки файлов
В следующей таблице кратко описаны системные вызовы, связанные с обработкой файлов.
%eax | Имя | %ebx | %ecx | %edx |
---|---|---|---|---|
2 | sys_fork | struct pt_regs | — | — |
3 | sys_read | unsigned int | char * | size_t |
4 | sys_write | unsigned int | const char * | size_t |
5 | sys_open | const char * | int | int |
6 | sys_close | unsigned int | — | — |
8 | sys_creat | const char * | int | — |
19 | sys_lseek | unsigned int | off_t | unsigned int |
Шаги, необходимые для использования системных вызовов, такие же как мы обсуждали ранее:
Создание и открытие файла
Для создания и открытия файла выполните следующие задачи:
Системный вызов возвращает дескриптор файла созданного файла в регистр EAX, в случае ошибки код ошибки находится в регистре EAX.
Открытие существующего файла
Чтобы открыть существующий файл, выполните следующие задачи:
Системный вызов возвращает дескриптор файла созданного файла в регистре EAX, в случае ошибки код ошибки находится в регистре EAX.
Среди режимов доступа к файлам чаще всего используются: только чтение (0), только запись (1) и чтение-запись (2).
Чтение из файла
Для чтения из файла выполните следующие задачи:
Системный вызов возвращает количество байтов, считанных в регистре EAX, в случае ошибки код ошибки находится в регистре EAX.
Запись в файл
Для записи в файл выполните следующие задачи:
Системный вызов возвращает фактическое количество байтов, записанных в регистр EAX, в случае ошибки код ошибки находится в регистре EAX.
Закрытие файла
Для закрытия файла выполните следующие задачи:
Системный вызов возвращает, в случае ошибки, код ошибки в регистре EAX.
Обновление файла
Для обновления файла выполните следующие задачи:
Исходная позиция может быть:
Системный вызов возвращает, в случае ошибки, код ошибки в регистре EAX.
Пример
Следующая программа создаёт и открывает файл с именем myfile.txt и записывает текст «Привет от HackWare!» в этом файле. Далее программа читает файл и сохраняет данные в буфере с именем info. Наконец, онf отображает текст как сохранённый в info.
Результат выполнения программы:
Ассемблер: Управление памятью
Системный вызов sys_brk() предоставляется ядром для выделения памяти без необходимости её перемещения позже. Этот вызов выделяет память прямо за изображением приложения в памяти. Эта системная функция позволяет вам установить максимальный доступный адрес в разделе данных.
Этот системный вызов принимает один параметр, который является наибольшим адресом памяти, который необходимо установить. Это значение сохраняется в регистре EBX.
Пример
Результат работы приведённого выше кода:
Связанные статьи:
Рекомендуется Вам:
7 комментариев to Введение в Ассемблер
Приветствую! Замечание абсолютно верное. Хотел сослаться на первоисточник (ссылка в самом верху), но там уже по-другому:
The main internal hardware of a PC consists of processor, memory, and registers. Registers are processor components that hold data and address. To execute a program, the system copies it from the external device into the internal memory. The processor executes the program instructions.
The fundamental unit of computer storage is a bit; it could be ON (1) or OFF (0) and a group of 8 related bits makes a byte on most of the modern computers.
So, the parity bit is used to make the number of bits in a byte odd. If the parity is even, the system assumes that there had been a parity error (though rare), which might have been caused due to hardware fault or electrical disturbance.
То есть неверную фразу просто убрали, из-за чего непонятно, почему в третьем абзаце продолжается разговор о бите чётности, хотя до этого он не упоминался.
Как говорится, «тот, кто лечится по медицинскому учебнику, рискует умереть от опечатки». Я переводил этот текст в рамках своего самообразования, то есть я тоже только осваиваю этот вопрос, поэтому я не могу исправлять чужие ошибки. Вывод: нужно читать более авторитетные источники, а не написанные энтузиастами тексты, которые переводят другие энтузиасты.
И, кстати, при критике не надо переходить на личности. Не ошибается только то, кто ничего не делает. В частности, вы написали несколько строк и тоже ошиблись.
Существует «even parity«, то есть «чётная чётность» и «odd parity«, то есть «нечётная чётность». В первом случае действительно подгоняется под чётное количество единиц, а во втором случае подгоняется под нечётное количество единиц. Я же не называю вас самоуверенным «гением», который не может написать две строки без фактической ошибки по вопросу, который гуглится за две секунды.
Исправлять неточности и ошибки общедоступных материалах и наработок сообщества, в том числе в open source — это здорово. Переходить на личности и пытаться кого-то зацепить, это НЕ круто.
Какой вы вежливый, прямо застыдили меня.
Про odd parity действительно не подумал.
Друзья, перевод данных основ по Ассемблеру завершён!
Если у вас возникли вопросы или проблемы с примерами, то пишите их в комментариях.
Если вам понравилось, то делитесь ссылками на данный вводный урок — если будет интерес, то будут подготовлены последующие части с интересными примерами программ на Ассемблере.
Двоичная арифметика
Следующая таблица иллюстрирует четыре простых правила для двоичного сложения:
(i) | (ii) | (iii) | (iv) |
---|---|---|---|
1 | |||
0 | 1 | 1 | 1 |
+0 | +0 | +1 | +1 |
=0 | =1 | =10 | =11 |
Эту таблицу нужно читать по столбцам сверху вниз. В первом столбце складываются 0 и 0 — в результате получается 0. Во втором примере складываются 1 и 0 (или 0 и 1 — без разницы), в результате получается 1. В третьем столбце складываются две единицы — в результате в текущей позиции получается 0, но на одну позицию влево добавляется единица. Если в этой позиции уже есть единица — то применяется это же правило, то есть в позиции пишется 0, и 1 передаётся влево. В четвёртом примере складываются три единицы — в результате, в текущей позиции записывается 1, и ещё одна 1 передаётся влево.
Ну конечно, бред. В двоичном счислении:
Попейте кофейку или проконсультируйтесь со своим учителем информатики, пока не поймёте эти элементарнейшие вещи, читать дальше смысла нет.
ИМХО, «ассемблер для крякера» вам пока рановато…
- Программирование на языке ассемблера 8086
- Программирование на языке ассемблера столяров