Ассемблер

Содержание
  1. Введение
  2. Погружение в assembler. Полный курс по программированию на асме от ][
  3. Программирование на Ассемблере для начинающих с примерами программ
  4. IDE для Assembler
  5. Программа «Hello world» на ассемблере
  6. Сложение двух чисел на assembler
  7. Программа суммы чисел на ассемблере
  8. Получение значения из командной строки на ассемблере
  9. Циклы в ассемблере
  10. Сумма элементов массива на assembler
  11. Почему Ассемблер — это круто, но сложно — Журнал «Код»: программирование без снобизма
  12. Как мыслит процессор
  13. Команды Ассемблера
  14. Пример: возвести число в куб
  15. Почему это круто
  16. Почему это сложно
  17. Для чего всё это
  18. Ассемблер
  19. [править] История
  20. [править] Асмофаги и студенты из Политеха
  21. [править] Ассемблер и Линукс
  22. [править] Ассемблер в кино
  23. [править] Область применения
  24. Программирование на языке ассемблера
  25. 1.1. Регистры общего назначения
  26. 1.2. Указатель команд
  27. 1.3. Регистр флагов
  28. 1.3.1. Флаги состояния
  29. 1.3.2. Управляющий флаг
  30. 1.3.3. Системные флаги и поле IOPL
  31. 1.4. Сегментные регистры
  32. 1.5. Использование стека
  33. 2.2. Целые числа
  34. 2.3. Символьные данные

Введение

Ассемблер

Скачать бесплатно книгу Ассемблер для начинающих

Для начала разберёмся с терминологией.

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

Язык программирования низкого уровня (низкоуровневый язык программирования) – это язык программирования, максимально приближённый к программированию в машинных кодах. В отличие от машинных кодов, в языке низкого уровня каждой команде соответствует не число, а сокращённое название команды (мнемоника). Например, команда ADD – это сокращение от слова ADDITION (сложение). Поэтому использование языка низкого уровня существенно упрощает написание и чтение программ (по сравнению с программированием в машинных кодах). Язык низкого уровня привязан к конкретному процессору. Например, если вы написали программу на языке низкого уровня для процессора PIC, то можете быть уверены, что она не будет работать с процессором AVR.

Язык программирования высокого уровня – это язык программирования, максимально приближённый к человеческому языку (обычно к английскому, но есть языки программирования на национальных языках, например, язык 1С основан на русском языке). Язык высокого уровня практически не привязан ни к конкретному процессору, ни к операционной системе (если не используются специфические директивы).

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

Ассемблер – это специальная программа, которая преобразует (компилирует) исходные тексты вашей программы, написанной на языке ассемблера, в исполняемый файл (файл с расширением EXE или COM). Если быть точным, то для создания исполняемого файла требуются дополнительные программы, а не только ассемблер. Но об этом позже…

В большинстве случаев говорят «ассемблер», а подразумевают «язык ассемблера». Теперь вы знаете, что это разные вещи и так говорить не совсем правильно. Хотя все программисты вас поймут.

ВАЖНО!
В отличие от языков высокого уровня, таких, как Паскаль, Бейсик и т.п., для КАЖДОГО АССЕМБЛЕРА существует СВОЙ ЯЗЫК АССЕМБЛЕРА. Это правило в корне отличает язык ассемблера от языков высокого уровня. Исходные тексты программы (или просто «исходники»), написанной на языке высокого уровня, вы в большинстве случаев можете откомпилировать разными компиляторами для разных процессоров и разных операционных систем. С ассемблерными исходниками это сделать будет намного сложнее. Конечно, эта разница почти не ощутима для разных ассемблеров, которые предназначены для одинаковых процессоров. Но в том то и дело, что для КАЖДОГО ПРОЦЕССОРА существует СВОЙ АССЕМБЛЕР и СВОЙ ЯЗЫК АССЕМБЛЕРА. В этом смысле программировать на языках высокого уровня гораздо проще. Однако за все удовольствия надо платить. В случае с языками высокого уровня мы можем столкнуться с такими вещами как больший размер исполняемого файла, худшее быстродействие и т.п.

В этой книге мы будем говорить только о программировании для компьютеров с процессорами Intel (или совместимыми). Для того чтобы на практике проверить приведённые в книге примеры, вам потребуются следующие программы (или хотя бы некоторые из них):

  1. Emu8086. Хорошая программа, особенно для новичков.

    Включает в себя редактор исходного кода и некоторые другие полезные вещи. Работает в Windows, хотя программы пишутся под DOS. К сожалению, программа стоит денег (но оно того стоит))). Подробности см. на сайте http://www.emu8086.com.

  2. TASM – Турбо Ассемблер от фирмы Borland. Можно создавать программы как для DOS так и для Windows.

    Тоже стоит денег и в данный момент уже не поддерживается (да и фирмы Borland уже не существует). А вообще вещь хорошая.

  3. MASM – Ассемблер от компании Microsoft (расшифровывается как МАКРО ассемблер, а не Microsoft Assembler, как думают многие непосвящённые). Пожалуй, самый популярный ассемблер для процессоров Intel. Поддерживается до сих пор.

    Условно бесплатная программа. То есть, если вы будете покупать её отдельно, то она будет стоить денег. Но она доступна бесплатно подписчикам MSDN и входит в пакет программ Visual Studio от Microsoft.

  4. WASM – ассемблер от компании Watcom. Как и все другие, обладает преимуществами и недостатками.

  5. Debug — обладает скромными возможностями, но имеет большой плюс — входит в стандартный набор Windows. Поищите ее в папке WINDOWS\COMMAND или WINDOWS\SYSTEM32. Если не найдете, тогда в других папках каталога WINDOWS.
  6. Желательно также иметь какой-нибудь шестнадцатеричный редактор.

    Не помешает и досовский файловый менеджер, например Волков Коммандер (VC) или Нортон Коммандер (NC). С их помощью можно также посмотреть шестнадцатеричные коды файла, но редактировать нельзя. Бесплатных шестнадцатеричных редакторов в Интернете довольно много. Вот один из них: McAfee FileInsight v2.1.

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

  7. Текстовый редактор. Необходим для написания исходных текстов ваших программ.

    Могу порекомендовать бесплатный редактор PSPad, который поддерживает множество языков программирования, в том числе и язык Ассемблера.

Все представленные в этой книге программы (и примеры программ) проверены на работоспособность. И именно эти программы используются для реализации примеров программ, приведённых в данной книге.

И еще – исходный код, написанный, например для Emu8086, будет немного отличаться от кода, написанного, например, для TASM. Эти отличия будут оговорены.

Большая часть программ, приведённых в книге, написана для MASM. Во-первых, потому что этот ассемблер наиболее популярен и до сих пор поддерживается. Во-вторых, потому что он поставляется с MSDN и с пакетом программ Visual Studio от Microsoft. Ну и в третьих, потому что я являюсь счастливым обладателем лицензионной копии MASM.

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

Источник: http://www.av-assembler.ru/asm/afd/introduction.htm

Погружение в assembler. Полный курс по программированию на асме от ][

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

Источник: https://xakep.ru/2017/09/11/asm-course-1/

Программирование на Ассемблере для начинающих с примерами программ

Ассемблер

Многие считают, что Assembler – уже устаревший и нигде не используемый язык, однако в основном это молодые люди, которые не занимаются профессионально системным программированием.

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

Для понимания этого древнего ЯП, для начала стоит попрактиковаться с простыми программами, которые лучше всего объясняют функционал Ассемблера.

IDE для Assembler

Первый вопрос: в какой среде разработки программировать на Ассемблере? Ответ однозначный – MASM32. Это стандартная программа, которую используют для данного ЯП.

Скачать её можно на официальном сайте masm32.com в виде архива, который нужно будет распаковать и после запустить инсталлятор install.exe.

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

Перед работой главное не забыть дописать в системную переменную PATH строчку:

С:\masm32\bin

Программа «Hello world» на ассемблере

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

.386 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib .data msg_title db «Title», 0 msg_message db «Hello world», 0 .code start: invoke MessageBox, 0, addr msg_message, addr msg_title, MB_OK invoke ExitProcess, 0 end start

Для начала запускаем редактор qeditor.exe в папке с установленной MASM32, и в нём пишем код программы. После сохраняем его в виде файла с расширением «.asm», и билдим программу с помощью пункта меню «Project» → «Build all». Если в коде нет ошибок, программа успешно скомпилируется, и на выходе мы получим готовый exe-файл, который покажет окно Windows с надписью «Hello world».

Сложение двух чисел на assembler

В этом случае мы смотрим, равна ли сумма чисел нулю, или же нет. Если да, то на экране появляется соответствующее сообщение об этом, и, если же нет – появляется иное уведомление.

.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: mov eax, 123 mov ebx, -90 add eax, ebx test eax, eax jz zero invoke MessageBox, 0, chr$(«В eax не 0!»), chr$(«Info»), 0 jmp lexit zero: invoke MessageBox, 0, chr$(«В eax 0!»), chr$(«Info»), 0 lexit: invoke ExitProcess, 0 end start

Здесь мы используем так называемые метки и специальные команды с их использованием (jz, jmp, test). Разберём подробнее:

  • test – используется для логического сравнения переменных (операндов) в виде байтов, слов, или двойных слов. Для сравнения команда использует логическое умножение, и смотрит на биты: если они равны 1, то и бит результата будет равен 1, в противном случае – 0. Если мы получили 0, ставятся флаги совместно с ZF (zero flag), которые будут равны 1. Далее результаты анализируются на основе ZF.
  • jnz – в случае, если флаг ZF нигде не был поставлен, производится переход по данной метке. Зачастую эта команда применяется, если в программе есть операции сравнения, которые как-либо влияют на результат ZF. К таким как раз и относятся test и cmp.
  • jz – если флаг ZF всё же был установлен, выполняется переход по метке.
  • jmp – независимо от того, есть ZF, или же нет, производится переход по метке.

Программа суммы чисел на ассемблере

Примитивная программа, которая показывает процесс суммирования двух переменных:

.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data msg_title db «Title», 0 A DB 1h B DB 2h buffer db 128 dup(?) format db «%d»,0 .code start: MOV AL, A ADD AL, B invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start

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

Получение значения из командной строки на ассемблере

Одно из важных основных действий в программировании – это получить данные из консоли для их дальнейшей обработки. В данном случае мы их получаем из командной строки и выводим в окне Windows:

.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: call GetCommandLine ; результат будет помещен в eax push 0 push chr$(«Command Line») push eax ; текст для вывода берем из eax push 0 call MessageBox push 0 call ExitProcess end start

Также можно воспользоваться альтернативным методом:

.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data .code start: call GetCommandLine ; результат будет помещен в eax invoke GetCommandLine invoke MessageBox, 0, eax, chr$(«Command Line»), 0 invoke ExitProcess, 0 push 0 call ExitProcess end start

Здесь используется invoke – специальный макрос, с помощью которого упрощается код программы. Во время компиляции макрос-команды преобразовываются в команды Ассемблера.

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

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

Циклы в ассемблере

Вариант использования:

.data msg_title db «Title», 0 A DB 1h buffer db 128 dup(?) format db «%d»,0 .code start: mov AL, A .REPEAT inc AL .UNTIL AL==7 invoke wsprintf, addr buffer, addr format, AL invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start .data msg_title db «Title», 0 buffer db 128 dup(?) format db «%d»,0 .code start: mov eax, 1 mov edx, 1 .WHILE edx==1 inc eax .IF eax==7 .BREAK .ENDIF .ENDW invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0

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

Для того, чтобы прервать работу цикла, используется директива «.BREAK». Она может как останавливать цикл, так и продолжать его действие после «паузы».

Также можно прервать выполнение кода программы и проверить условие repeat и while с помощью директивы «.CONTINUE».

Сумма элементов массива на assembler

Здесь мы суммируем значения переменных в массиве, используя цикл «for»:

.486 .model flat, stdcall option casemap: none include /masm32/include/windows.inc include /masm32/include/user32.inc include /masm32/include/kernel32.inc includelib /masm32/lib/user32.lib includelib /masm32/lib/kernel32.lib include /masm32/macros/macros.asm uselib masm32, comctl32, ws2_32 .data msg_title db «Title», 0 A DB 1h x dd 0,1,2,3,4,5,6,7,8,9,10,11 n dd 12 buffer db 128 dup(?) format db «%d»,0 .code start: mov eax, 0 mov ecx, n mov ebx, 0 L: add eax, x[ebx] add ebx, type x dec ecx cmp ecx, 0 jne L invoke wsprintf, addr buffer, addr format, eax invoke MessageBox, 0, addr buffer, addr msg_title, MB_OK invoke ExitProcess, 0 end start

Команда dec, как и inc, меняет значение операнда на единицу, только в противоположную сторону, на -1. А вот cmp сравнивает переменные методом вычитания: отнимает одно значение из второго, и, в зависимости от результата ставит соответствующие флаги.

С помощью команды jne выполняется переход по метке, основываясь на результате сравнения переменных. Если он отрицательный – происходит переход, а если операнды не равняются друг другу, переход не осуществляется.

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

Источник: https://evilinside.ru/assembler-dlya-nachinayushhix-primery-prostyx-programm/

Почему Ассемблер — это круто, но сложно — Журнал «Код»: программирование без снобизма

Ассемблер

Есть высо­ко­уров­не­вые язы­ки — это те, где вы гово­ри­те if — else, print, echo, function и так далее. «Высо­кий уро­вень» озна­ча­ет, что вы гово­ри­те с ком­пью­те­ром более-менее чело­ве­че­ским язы­ком. Дру­гой чело­век может не понять, что имен­но у вас напи­са­но в коде, но он хотя бы смо­жет про­чи­тать сло­ва.

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

Ассем­блер — это соби­ра­тель­ное назва­ние язы­ков низ­ко­го уров­ня: код всё ещё пишет чело­век, но он уже гораз­до бли­же к прин­ци­пам рабо­ты ком­пью­те­ра, чем к прин­ци­пам мыш­ле­ния чело­ве­ка.

Вари­ан­тов Ассем­бле­ра доволь­но мно­го. Но так как все они рабо­та­ют по оди­на­ко­во­му прин­ци­пу и исполь­зу­ют (в основ­ном) оди­на­ко­вый син­так­сис, мы будем все подоб­ные язы­ки назы­вать общим сло­вом «Ассем­блер».

Как мыслит процессор

Что­бы понять, как рабо­та­ет Ассем­блер и поче­му он рабо­та­ет имен­но так, нам нуж­но немно­го разо­брать­ся с внут­рен­ним устрой­ством про­цес­со­ра.

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

Реги­стры быва­ют раз­но­го вида и назна­че­ния: одни слу­жат, что­бы хра­нить инфор­ма­цию; дру­гие сооб­ща­ют о состо­я­нии про­цес­со­ра; тре­тьи исполь­зу­ют­ся как нави­га­то­ры, что­бы про­цес­сор знал, куда идти даль­ше, и так далее. Подроб­нее — в рас­хло­пе ↓

Обще­го назна­че­ния. Это 8 реги­стров, каж­дый из кото­рых может хра­нить все­го 4 бай­та инфор­ма­ции. Такой регистр мож­но раз­де­лить на 2 или 4 части и рабо­тать с ними как с отдель­ны­ми ячей­ка­ми.

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

Регистр фла­гов. Флаг — какое-то свой­ство про­цес­со­ра. Напри­мер, если уста­нов­лен флаг пере­пол­не­ния, зна­чит про­цес­сор полу­чил в ито­ге такое чис­ло, кото­рое не поме­ща­ет­ся в нуж­ную ячей­ку памя­ти. Он туда кла­дёт то, что поме­ща­ет­ся, и ста­вит в этот флаг циф­ру 1. Она — сиг­нал про­грам­ми­сту, что что-то пошло не так.

Фла­гов в про­цес­со­ре мно­го, какие-то мож­но менять вруч­ную, и они будут вли­ять на вычис­ле­ния, а какие-то мож­но про­сто смот­реть и делать выво­ды. Фла­ги — как сиг­наль­ные лам­пы на пане­ли при­бо­ров в само­лё­те. Они что-то озна­ча­ют, но толь­ко само­лёт и пилот зна­ют, что имен­но.

Сег­мент­ные реги­стры. Нуж­ны были для того, что­бы рабо­тать с опе­ра­тив­ной памя­тью и полу­чать доступ к любой ячей­ке. Сей­час такие реги­стры име­ют по 32 бита, и это­го доста­точ­но, что­бы полу­чить 4 гига­бай­та опе­ра­тив­ки. Для про­грам­мы на Ассем­бле­ре это­го обыч­но хва­та­ет.

Так вот: всё, с чем рабо­та­ет Ассем­блер, — это коман­ды про­цес­со­ра, пере­мен­ные и реги­стры.

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

Команды Ассемблера

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

Любая коман­да на этом язы­ке выгля­дит так:

[:] [] [;]

Мет­ка — это имя для фраг­мен­та кода. Напри­мер, вы хоти­те отдель­но поме­тить место, где начи­на­ет­ся рабо­та с жёст­ким дис­ком, что­бы было лег­че читать код. Ещё мет­ка нуж­на, что­бы в дру­гом участ­ке про­грам­мы мож­но было напи­сать её имя и сра­зу пере­прыг­нуть к нуж­но­му кус­ку кода.

Коман­да — слу­жеб­ное сло­во для про­цес­со­ра, кото­рое он дол­жен выпол­нить. Спе­ци­аль­ные ком­пи­ля­то­ры пере­во­дят такие коман­ды в машин­ный код.

Это сде­ла­но для того, что­бы не запо­ми­нать сами машин­ные коман­ды, а исполь­зо­вать вме­сто них какие-то бук­вен­ные обо­зна­че­ния, кото­рые про­ще запом­нить.

В этом, соб­ствен­но, и выра­жа­ет­ся чело­веч­ность Ассем­бле­ра: коман­ды в нём хотя бы отда­лён­но напо­ми­на­ют чело­ве­че­ские сло­ва.

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

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

mov eax, ebx ; Пересылаем значение регистра EBX в регистр EAX

mov x, 0 ; Записываем в переменную x значение 0

add eax, х ; Складываем значение регистра ЕАХ и переменной х, результат отправится в регистр ЕАХ

Здесь нет меток, пер­вы­ми идут коман­ды (mov или add), а за ними — опе­ран­ды и ком­мен­та­рии.

Пример: возвести число в куб

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

Пер­вый вари­ант

mov al, x ; Пересылаем x в регистр AL

imul al ; Умножаем регистр AL на себя, AX = x * x

movsx bx, x ; Пересылаем x в регистр BX со знаковым расширением

imul bx ; Умножаем AX на BX. Результат разместится в DX:AX

Вто­рой вари­ант

mov al, x ; Пересылаем x в регистр AL

imul al ; Умножаем регистр AL на себя, AX = x * x

cwde ; Расширяем AX до EAX

movsx ebx, x ; Пересылаем x в регистр EBX со знаковым расширением

imul ebx ; Умножаем EAX на EBX. Поскольку x – 1-байтовая переменная, результат благополучно помещается в EAX

На любом высо­ко­уров­не­вом язы­ке воз­ве­сти чис­ло в куб мож­но одной стро­кой. Напри­мер:

x = Math.pow(x,3);
x := exp(ln(x) * 3);
на худой конец x = x*x*x.

Хит­рость в том, что когда каж­дая из этих строк будет све­де­на к машин­но­му коду, это­го кода может быть и 5 команд, и 10, и 50, и даже 100. Чего сто­ит вызов объ­ек­та Math и его мето­да pow: толь­ко на эту слу­жеб­ную опе­ра­цию (ещё до само­го воз­ве­де­ния в куб) может уйти несколь­ко сотен и даже тысяч машин­ных команд.

А на Ассем­бле­ре это гаран­ти­ро­ван­но пять команд. Ну, или как реа­ли­зу­е­те.

Почему это круто

Ассем­блер поз­во­ля­ет рабо­тать с про­цес­со­ром и памя­тью напря­мую — и делать это очень быст­ро. Дело в том, что в Ассем­бле­ре почти не тра­тит­ся зря про­цес­сор­ное вре­мя.

Если про­цес­сор рабо­та­ет на часто­те 3 гига­гер­ца — а это при­мер­но 3 мил­ли­ар­да про­цес­сор­ных команд в секун­ду, — то очень хоро­ший код на Ассем­бле­ре будет выпол­нять при­мер­но 2,5 мил­ли­ар­да команд в секун­ду.

Для срав­не­ния, JavaScript или Python выпол­нят в тыся­чу раз мень­ше команд за то же вре­мя.

Ещё про­грам­мы на Ассем­бле­ре зани­ма­ют очень мало места в памя­ти. Имен­но поэто­му на этом язы­ке пишут драй­ве­ры, кото­рые встра­и­ва­ют пря­мо в устрой­ства, или управ­ля­ю­щие про­грам­мы, кото­рые зани­ма­ют несколь­ко кило­байт.

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

А всё пото­му, что она напи­са­на для кон­крет­но­го про­цес­со­ра и исполь­зу­ет его воз­мож­но­сти на сто про­цен­тов.

Спра­вед­ли­во­сти ради отме­тим, что совре­мен­ные ком­пи­ля­то­ры С++ дают машин­ный код, близ­кий по быст­ро­дей­ствию к Ассем­бле­ру, но всё рав­но немно­го усту­па­ют ему.

Почему это сложно

Для того, что­бы писать про­грам­мы на Ассем­бле­ре, нуж­но очень любить крем­ний:

  • пони­мать архи­тек­ту­ру про­цес­со­ра;
  • знать устрой­ство желе­за, кото­рое рабо­та­ет с этим про­цес­со­ром;
  • знать все коман­ды, кото­рые отно­сят­ся имен­но к это­му типу про­цес­со­ров;
  • уметь рабо­тать с дан­ны­ми в побай­то­вом режи­ме (забудь­те о стро­ках и мас­си­вах, ведь ваш мак­си­мум — это одна бук­ва);
  • пони­мать, как в огра­ни­чен­ных усло­ви­ях реа­ли­зо­вать нуж­ную функ­ци­о­наль­ность.

Теперь добавь­те к это­му отсут­ствие боль­шин­ства при­выч­ных биб­лио­тек для рабо­ты с чем угод­но, слож­ность чте­ния тек­ста про­грам­мы, мед­лен­ную ско­рость раз­ра­бот­ки — и вы полу­чи­те пол­ное пред­став­ле­ние о про­грам­ми­ро­ва­нии на Ассем­бле­ре.

Для чего всё это

Ассем­блер неза­ме­ним в таких вещах:

  • драй­ве­ры;
  • про­грам­ми­ро­ва­ние мик­ро­кон­трол­ле­ров и встра­и­ва­е­мых про­цес­со­ров;
  • кус­ки опе­ра­ци­он­ных систем, где важ­но обес­пе­чить ско­рость рабо­ты;
  • анти­ви­ру­сы (и виру­сы).

На самом деле на Ассем­бле­ре мож­но даже запи­лить свой сайт с фору­мом, если у про­грам­ми­ста хва­та­ет ква­ли­фи­ка­ции. Но чаще все­го Ассем­блер исполь­зу­ют там, где даже ско­ро­сти и воз­мож­но­стей C++ недо­ста­точ­но.

Источник: https://thecode.media/assembler/

Ассемблер

Ассемблер

Материал из Lurkmore

Народ требует хлеба и зрелищ!Народ требует иллюстраций к статье!В конце концов, если бы мы хотели почитать, мы бы пошли в библиотеку.
«Нам не дано предугадать, как слово наше отзовется…»
— Ф. И. Тютчев программирует на Ассемблере

Ассемблер или asm (англ. assembler — сборщик) — утилита, транслирующая исходники программы на языке ассемблера собственно в машинный код, то есть на язык бездушной машины. Для обратного превращения существует дизассемблер (англ.

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

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

Но это не так уж и важно, так как нет процессора кроме x86 и фон Нейман пророк его, а всякие Жабы и дотнеты вообще используют собственные виртуальные машины с собственным асмом — похуй, что в тысячу раз медленнее, зато переносимость и безопасность, блджад! Также, используя макросы, юный друг сможет убедиться в отсутствии принципиальной разницы между асмом и всеми остальными «православными» языками.

[править] История

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

Затем человек начал совершенствовать эту технику, постепенно перекладывая труд по генерации кода на плечи самой машины.

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

Простейший транслятор (ассемблер первого поколения) позволял фактически просто писать машинные команды на «человеческом» языке, что позволило программерам немного расслабиться.

Достаточно быстро туда добавились многочисленные свистелки и перделки, нарушающие Истинную Красоту Программирования На Ассемблере, но еще больше экономящие время и силы (а значит и деньги), как то макросы, библиотеки и прочее.

Затем появились языки высокого уровня и компиляторы (более интеллектуальные генераторы кода с более понятного человеку языка) и интерпретаторы (исполнители человекописанной программы на лету).

Которые совершенствовались, совершенствовались, совершенствовались, и досовершенствовались до «программирования мышкой», а компиляторы научились генерировать код, которому по скорости написанный человеком начал сливать[1]. Такие дела.

В целом история языков программирования протекает в направлении от программирования на языке компьютера до манипуляции абстракциями вроде Послать.

Лесом(всех(кому не нравится эта статья)) Ну или (послать (лесом (всех (кому-не-нравится «эта-статья»)))) или послать :: Куда -> [Люди] -> Stringпослать куда = map (\x -> (show x) ++ «, иди » ++ (show куда))кванторВкуса :: (ОбъектЧувства a) => ТипКвантора -> ТипОтношения -> a -> [Люди] посылаем :: Stringпосылаем = let быдло = кванторВкуса Все НеНравится этаСтатья in послать быдло

На языке ассемблера этот код занял бы более 9000 строк. И потребовал бы долгой и кропотливой работы по своему созданию. Зато, в отличие от Шindoшs, оно бы работало, а не только свистело, пердело, выдавало красивые иконки и жрало 640K памяти.

[править] Асмофаги и студенты из Политеха

Среди программистов с длинной бородой, видевших еще те компьютеры-динозавры, есть такие, которые очень трепетно относятся к напейсанию кода, и считают, что ассемблер — это труЪ, а языки высокого уровня — шлак и унылое говно, предназначенные исключительно для быдлокодерства.

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

Степень владения языком ассемблера считается мерилом отличия программиста от быдлокодера. Наиболее тяжелый клинический случай представляет молочный брат Линуса расовый чухонец Вилле Турьянмаа, написавший целую ОС — MenuetOS на чистом ассемблере.

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

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

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

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

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

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

Но когда стоимость каждого лишнего не то что мегабайта, а полубайта в ОЗУ становится критичной (ибо их просто нет, например на каком-нибудь запущенном много лет назад спутнике, до которого можно достучаться по радио, но никак физически), выливаются в миллиарды и даже жизни (встроенный софт бортовых систем в авиации и космонавтике) — ассемблер strikes back! К примеру, софт для кораблей «Союз ТМА-М», «Прогресс М-М» написан на Си (по крайней мере, российский сегмент МКС). ПО ЦВМов «Бурана» писалось на ПРОЛ-2. «Союз ТМА» — не на Си, у него БЦВК «Аргон-16», сначала программировали его на ассемблере, в поздних машинах транслировали на высокоуровневый язык. Спутники «Ямал» и «Белка» написаны на Си.

[править] Ассемблер и Линукс

Хотя программирование на ассемблере в Unix-мире считается моветоном, поскольку нарушает один из основополагающих принципов unix way «жертвуй производительностью ради переносимости», даже тут находится место разногласиям, способным вылиться в настоящую драму.

Дело в том, что встроенный ассемблер большинства Unix-ов (и Linux в том числе) использует синтаксис (AT&T-синтаксис), в корне отличающийся от оригинального Intel-синтаксиса.

А поскольку Intel-синтаксис у большинства труЪ-линуксоидов прочно ассоциируется с маздаем, он объявляется ересью, и всем, кто пользуется nasm, yasm или fasm пророчатся вечные муки, страшный суд, ад и погибель, хотя gas тоже владеет интеловским синтаксисом. Правда все ELF-инфекторы всё же приходится писать на старом добром асме.

Алсо какой либо годной документации по ассемблеру AT&T в интернете хрен найдешь. По этой причине программистов на AT&T крайне мало. А по этой причине некому писать документацию.

[править] Ассемблер в кино

Оно.

Как минимум один раз снялся в кино, но зато в каком! В первой части Терминатора можно видеть как Киборгъ предпочитает MOS Technology 6510/8500 ассемблер.

Необходимое пояснение. На скриншоте видна программка из чередующихся инструкций LDA-STA-LDA-STA…(load-store-load-store). В семействе 6502 программы состоят чуть менее, чем полностью из LDA/LDY/LDX/STA/STX/STY вследствие наличия всего 3х 8-битных регистров.

Наличие почти полного отсутствия регистров компенсируется нулевой страницей памяти, откуда-куда постоянно приходится перекладывать байтики, так как хранить их более негде. В данном ЦПУ 13 режимов адресации на всего 53 команды. Алсо порты ввода-вывода замаплены в адреса памяти, так что чтение-запись из-в порты осуществляется также этими командами.

Короче: программы в Терминаторе имеют вполне осмысленный вид и не являются кучей команд от балды. Подробнее: Этот удивительный Терминатор/48076.

Во второй части интерфейс Терминатора более очеловечен. Впрочем, это можно объяснить тем, что ПО для первого терминатора делал Скайнет, а для второго набыдлокодил Джон Коннор.

Алсо, ассемблер семейства 8080 (у нас серия КР580) засветился в давнем аниме «Megazone 23».

Алсо, в качестве исходника чат-бота Киса в фильме }{0TT@БЬ)Ч был показан исходник чат-бота Eliza на асме для калькулятора TI-83 Plus с процессором Z80.

Алсо, в фильме «Элизиум» (2013) пафосный топ-манагер кодит на асме.

[править] Область применения

  • Популярен для допиливания зацикливаемых кусков программы… в роли напильника. Перепиливание критических участков кода может принести PROFIT, а может и не принести. В любом случае, заниматься таким перепиливанием стоит только тогда, когда у вас на руках уже полностью работающий алгоритм, который можно было бы ускорить, а не наоборот.
  • Для использования в софтине новых команд, доступных в новых процессорах. Компилятор хоть и оптимизирует код высокого уровня при компиляции, но… практически никогда не способен генерировать инструкции из расширенных наборов команд типа AVX, СTV, XOP и т. п. Потому что команды в процессоры добавляют быстрее, чем в компиляторах появляется логика для генерации этих команд.
  • Используется для написания кода, создание которого невозможно (или затруднено) на языках высокого уровня, например, получение дампа памяти/стека. Даже когда аналог на языке высокого уровня возможен, профит от языка ассемблера может быть значительным в разы. Например, реализация подсчета среднего арифметического двух чисел с учётом переполнения для x86 процессоров занимает всего 2 команды (сложение с выставлением флага переноса и сдвиг с заёмом этого флага). Аналог на языке высокого уровня ((long) x + y) >> 1
    1. может не работать в принципе, ведь sizeof(long) == sizeof(int). Ну или может бомбануть, как это было на ракете Ариан-5 (64-ичные переменные с плавающей запятой float преобразовывались в 16 бит фиксированных, когда программа дошла до 32767+1, у ракеты включилась система «самоуничтожение на случай попадания в мир КГБ» прямо на космодроме.
    2. при компиляции конвертируется в чуть менее чем дохрена команд процессора.

Ещё пример: перемножение чисел с фиксированной запятой в языке, в который не вшита поддержка такого формата чисел. На ассемблере это умножение eax на edx с сохранением 64-битного результата в edx:eax с последующим сдвигом вправо (shrd). На ЯВУ это выглядит либо как (x*y)>>16, что не даёт верный результат при выходе результата умножения за 32 бита, либо как (int64(x)*y)>>16, что конвертируется в дохрена команд.Впрочем, в данных примерах современные компиляторы умеют правильно понимать намерения программиста и генерировать оптимальный код.

  • Также может использоваться задротами для написания очень компактных программ. Пример: клиент для аськи, занимающий в скомпилированном виде 34 КБ. Другой пример: тоже IM-клиент Faim 0.21 — 12.61 КБ. Отдельная дисциплина этой специальной олимпиады — демки.
  • В вузах изучается для вливания в МНУ студентоты познаний об устройстве и работе компьютера на уровне повыше кликанья мышкой по окошкам. Отличное лекарство от быдлокодерства (ну, а для оказавшихся неспособными осилить, соответственно — окончательное решение).
  • Является единственным языком программирования для создания достойных инфекторов — Cih, Sality, Sinowal.
  • Чуть более чем половина программ для GPU пишутся на ассемблере, наряду с использованием языков высокого уровня HLSL или GLSL. Хороший, годный шейдер обязательно содержит асму, ибо GPU — это принципиально узкое место для выделения графической составляющей в монитор (Есть, конечно и более современные методы типа использования OpenCL, но всё же ASM

Источник: https://lurkmore.to/%D0%90%D1%81%D1%81%D0%B5%D0%BC%D0%B1%D0%BB%D0%B5%D1%80

Программирование на языке ассемблера

Ассемблер

В данной части курса рассматриваются основы программирования на языке ассемблера для архитектуры Win32.

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

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

Также на языке ассемблера пишутся критичные по времени выполнения или расходованию памяти участки программы. Впоследствии они оформляются в виде подпрограмм и совмещаются с кодом на языке высокого уровня.

Регистры – это специальные ячейки памяти, расположенные непосредственно в процессоре.

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

Регистры можно разделить на регистры общего назначения, указатель команд, регистр флагов и сегментные регистры.

1.1. Регистры общего назначения

К регистрам общего назначения относится группа из 8 регистров, которые можно использовать в программе на языке ассемблера. Все регистры имеют размер 32 бита и могут быть разделены на 2 или более частей.

Как видно из рисунка, регистры ESI, EDI, ESP и EBP позволяют обращаться к младшим 16 битам по именам SI, DI, SP и BP соответственно, а регистры EAX, EBX, ECX и EDX позволяют обращаться как к младшим 16 битам (по именам AX, BX, CX и DX), так и к двум младшим байтам по отдельности (по именам AH/AL, BH/BL, CH/CL и DH/DL).

Названия регистров происходят от их назначения:

  • EAX/AX/AH/AL (accumulator register) – аккумулятор;
  • EBX/BX/BH/BL (base register) –регистр базы;
  • ECX/CX/CH/CL (counter register) – счётчик;
  • EDX/DX/DH/DL (data register) – регистр данных;
  • ESI/SI (source index register) – индекс источника;
  • EDI/DI (destination index register) – индекс приёмника (получателя);
  • ESP/SP (stack pointer register) – регистр указателя стека;
  • EBP/BP (base pointer register) – регистр указателя базы кадра стека.

Несмотря на существующую специализацию, все регистры можно использовать в любых машинных операциях. Однако надо учитывать тот факт, что некоторые команды работают только с определёнными регистрами. Например, команды умножения и деления используют регистры EAX и EDX для хранения исходных данных и результата операции. Команды управления циклом используют регистр ECX в качестве счётчика цикла.

Ещё один нюанс состоит в использовании регистров в качестве базы, т.е. хранилища адреса оперативной памяти. В качестве регистров базы можно использовать любые регистры, но желательно использовать регистры EBX, ESI, EDI или EBP. В этом случае размер машинной команды обычно бывает меньше.

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

1.2. Указатель команд

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

1.3. Регистр флагов

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

№ бита Обозначение Название Описание Тип флага
FLAGS
0 CF Carry Flag Флаг переноса Состояние
1 1 Зарезервирован
2 PF Parity Flag Флаг чётности Состояние
3 0 Зарезервирован
4 AF Auxiliary Carry Flag Вспомогательный флаг переноса Состояние
5 0 Зарезервирован
6 ZF Zero Flag Флаг нуля Состояние
7 SF Sign Flag Флаг знака Состояние
8 TF Trap Flag Флаг трассировки Системный
9 IF Interrupt Enable Flag Флаг разрешения прерываний Системный
10 DF Direction Flag Флаг направления Управляющий
11 OF Overflow Flag Флаг переполнения Состояние
12 IOPL I/O Privilege Level Уровень приоритета ввода-вывода Системный
13
14 NT Nested Task Флаг вложенности задач Системный
15 0 Зарезервирован
EFLAGS
16 RF Resume Flag Флаг возобновления Системный
17 VM Virtual-8086 Mode Режим виртуального процессора 8086 Системный
18 AC Alignment Check Проверка выравнивания Системный
19 VIF Virtual Interrupt Flag Виртуальный флаг разрешения прерывания Системный
20 VIP Virtual Interrupt Pending Ожидающее виртуальное прерывание Системный
21 ID ID Flag Проверка на доступность инструкции CPUID Системный
22 Зарезервированы
31

Значение флагов CF, DF и IF можно изменять напрямую в регистре флагов с помощью специальных инструкций (например, CLD для сброса флага направления), но нет инструкций, которые позволяют обратиться к регистру флагов как к обычному регистру. Однако можно сохранять регистр флагов в стек или регистр AH и восстанавливать регистр флагов из них с помощью инструкций LAHF, SAHF, PUSHF, PUSHFD, POPF и POPFD.

1.3.1. Флаги состояния

Флаги состояния (биты 0, 2, 4, 6, 7 и 11) отражают результат выполнения арифметических инструкций, таких как ADD, SUB, MUL, DIV.

  • Флаг переноса CF устанавливается при переносе из старшего значащего бита/заёме в старший значащий бит и показывает наличие переполнения в беззнаковой целочисленной арифметике.

    Также используется в длинной арифметике.

  • Флаг чётности PF устанавливается, если младший значащий байт результата содержит чётное число единичных битов.

    Изначально этот флаг был ориентирован на использование в коммуникационных программах: при передаче данных по линиям связи для контроля мог также передаваться бит чётности и инструкции для проверки флага чётности облегчали проверку целостности данных.

  • Вспомогательный флаг переноса AF устанавливается при переносе из бита 3-го результата/заёме в 3-ий бит результата. Этот флаг ориентирован на использование в двоично-десятичной (binary coded decimal, BCD) арифметике.
  • Флаг нуля ZF устанавливается, если результат равен нулю.
  • Флаг знака SF равен значению старшего значащего бита результата, который является знаковым битом в знаковой арифметике.
  • Флаг переполнения OF устанавливается, если целочисленный результат слишком длинный для размещения в целевом операнде (регистре или ячейке памяти). Этот флаг показывает наличие переполнения в знаковой целочисленной арифметике.

Из перечисленных флагов только флаг CF можно изменять напрямую с помощью инструкций STC, CLC и CMC.

Флаги состояния позволяют одной и той же арифметической инструкции выдавать результат трёх различных типов: беззнаковое, знаковое и двоично-десятичное (BCD) целое число.

Если результат считать беззнаковым числом, то флаг CF показывает условие переполнения (перенос или заём), для знакового результата перенос или заём показывает флаг OF, а для BCD-результата перенос/заём показывает флаг AF.

Флаг SF отражает знак знакового результата, флаг ZF отражает и беззнаковый, и знаковый нулевой результат.

В длинной целочисленной арифметике флаг CF используется совместно с инструкциями сложения с переносом (ADC) и вычитания с заёмом (SBB) для распространения переноса или заёма из одного вычисляемого разряда длинного числа в другой.

Инструкции условного перехода Jcc (переход по условию cc), SETcc (установить значение байта-результата в зависимости от условия cc), LOOPcc (организация цикла) и CMOVcc (условное копирование) используют один или несколько флагов состояния для проверки условия. Например, инструкция перехода JLE (jump if less or equal – переход, если «меньше или равно») проверяет условие «ZF = 1 или SF ≠ OF».

Флаг PF был введён для совместимости с другими микропроцессорными архитектурами и по прямому назначению используется редко.

Более распространено его использование совместно с остальными флагами состояния в арифметике с плавающей запятой: инструкции сравнения (FCOM, FCOMP и т. п.

) в математическом сопроцессоре устанавливают в нём флаги-условия C0, C1, C2 и C3, и эти флаги можно скопировать в регистр флагов.

Для этого рекомендуется использовать инструкцию FSTSW  AX для сохранения слова состояния сопроцессора в регистре AX и инструкцию SAHF для последующего копирования содержимого регистра AH в младшие 8 битов регистра флагов, при этом C0 попадает во флаг CF, C2 – в PF, а C3 – в ZF. Флаг C2 устанавливается, например, в случае несравнимых аргументов (NaN или неподдерживаемый формат) в инструкции сравнения FUCOM.

1.3.2. Управляющий флаг

Флаг направления DF (бит 10 в регистре флагов) управляет строковыми инструкциями (MOVS, CMPS, SCAS, LODS и STOS) – установка флага заставляет уменьшать адреса (обрабатывать строки от старших адресов к младшим), обнуление заставляет увеличивать адреса. Инструкции STD и CLD соответственно устанавливают и сбрасывают флаг DF.

1.3.3. Системные флаги и поле IOPL

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

  • Флаг разрешения прерываний IF – обнуление этого флага запрещает отвечать на маскируемые запросы на прерывание.
  • Флаг трассировки TF – установка этого флага разрешает пошаговый режим отладки, когда после каждой выполненной инструкции происходит прерывание программы и вызов специального обработчика прерывания.
  • Поле IOPL показывает уровень приоритета ввода-вывода исполняемой программы или задачи: чтобы программа или задача могла выполнять инструкции ввода-вывода или менять флаг IF, её текущий уровень приоритета (CPL) должен быть ≤ IOPL.
  • Флаг вложенности задач NT – этот флаг устанавливается, когда текущая задача «вложена» в другую, прерванную задачу, и сегмент состояния TSS текущей задачи обеспечивает обратную связь с TSS предыдущей задачи. Флаг NT проверяется инструкцией IRET для определения типа возврата – межзадачного или внутризадачного.
  • Флаг возобновления RF используется для маскирования ошибок отладки.
  • VM – установка этого флага в защищённом режиме вызывает переключение в режим виртуального 8086.
  • Флаг проверки выравнивания AC – установка этого флага вместе с битом AM в регистре CR0 включает контроль выравнивания операндов при обращениях к памяти: обращение к невыравненному операнду вызывает исключительную ситуацию.
  • VIF – виртуальная копия флага IF; используется совместно с флагом VIP.
  • VIP – устанавливается для указания наличия отложенного прерывания.
  • ID – возможность программно изменить этот флаг в регистре флагов указывает на поддержку инструкции CPUID.

1.4. Сегментные регистры

Процессор имеет 6 так называемых сегментных регистров: CS, DS, SS, ES, FS и GS. Их существование обусловлено спецификой организации и использования оперативной памяти.

16-битные регистры могли адресовать только 64 Кб оперативной памяти, что явно недостаточно для более или менее приличной программы. Поэтому память программе выделялась в виде нескольких сегментов, которые имели размер 64 Кб.

При этом абсолютные адреса были 20-битными, что позволяло адресовать уже 1 Мб оперативной памяти. Возникает вопрос – как имея 16-битные регистры хранить 20-битные адреса? Для решения этой задачи адрес разбивался на базу и смещение.

База – это адрес начала сегмента, а смещение – это номер байта внутри сегмента. На адрес начала сегмента накладывалось ограничение – он должен был быть кратен 16. При этом последние 4 бита были равны 0 и не хранились, а подразумевались. Таким образом, получались две 16-битные части адреса.

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

Сегментные регистры использовались для хранения адреса начала сегмента кода (CS – code segment), сегмента данных (DS – data segment) и сегмента стека (SS – stack segment). Регистры ES, FS и GS были добавлены позже.

Существовало несколько моделей памяти, каждая из которых подразумевала выделение программе одного или нескольких сегментов кода и одного или нескольких сегментов данных: tiny, small, medium, compact, large и huge.

Для команд языка ассемблера существовали определённые соглашения: адреса перехода сегментировались по регистру CS, обращения к данным сегментировались по регистру DS, а обращения к стеку – по регистру SS.

Если программе выделялось несколько сегментов для кода или данных, то приходилось менять значения в регистрах CS и DS для обращения к другому сегменту. Существовали так называемые «ближние» и «дальние» переходы.

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

32-битные регистры позволяют адресовать 4 Гб памяти, что уже достаточно для любой программы. Каждую Win32-программу Windows запускает в отдельном виртуальном пространстве.

Это означает, что каждая Win32-программа будет иметь 4-х гигабайтовое адресное пространство, но вовсе не означает, что каждая программа имеет 4 Гб физической памяти, а только то, что программа может обращаться по любому адресу в этих пределах.

А Windows сделает все необходимое, чтобы память, к которой программа обращается, «существовала». Конечно, программа должна придерживаться правил, установленных Windows, иначе возникает ошибка General Protection Fault.

Под архитектурой Win32 отпала необходимость в разделении адреса на базу и смещение, и необходимость в моделях памяти. На 32-битной архитектуре существует только одна модель памяти – flat (сплошная или плоская). Сегментные регистры остались, но используются по-другому1.

Раньше необходимо было связывать отдельные части программы с тем или иным сегментным регистром и сохранять/восстанавливать регистр DS при переходе к другому сегменту данных или явно сегментировать данные по другому регистру.

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

1.5. Использование стека

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

Как известно, стек – это область памяти, при работе с которой необходимо соблюдать определённые правила, а именно: данные, которые попали в стек первыми, извлекаются оттуда последними.

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

Пусть у нас есть функция f1, которая вызывает функцию f2, а функция f2, в свою очередь, вызывает функцию f3.

При вызове функции f1 ей отводится определённое место в стеке под локальные данные. Это место отводится путём вычитания из регистра ESP значения, равного размеру требуемой памяти.

Минимальный размер отводимой памяти равен 4 байтам, т.е. даже если процедуре требуется 1 байт, она должна занять 4 байта.

Функция f1 выполняет некоторые действия, после чего вызывает функцию f2. Функция f2 также отводит себе место в стеке, вычитая некоторое значение из регистра ESP. При этом локальные данные функций f1 и f2 размещаются в разных областях памяти.

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

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

Аналогично функция f2 должна при выходе восстановить значение регистра ESP, которое было до её вызова.

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

Но каждая процедура может обращаться к своей области стека произвольным образом.

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

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

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

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

Локальные данные автоматически не инициализируются.

Если в вышеприведённом примере функция f2 после функции f3 вызовет функцию f4, то функция f4 займёт в стеке место, которое до этого было занято функцией f3, таким образом, функции f4 «в наследство» достанутся данные функции f3. Поэтому каждая процедура обязательно должна заботиться об инициализации своих локальных данных.

Понятие идентификатора в языке ассемблера ничем не отличается от понятия идентификатора в других языках. Можно использовать латинские буквы, цифры и знаки _ . ? @ $, причём точка может быть только первым символом идентификатора. Большие и маленькие буквы считаются эквивалентными.

2.2. Целые числа

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

Для задания системы счисления в конце числа ставится буква b, o/q, d или h соответственно.

Шестнадцатеричные числа, которые начинаются с «буквенной» цифры, должны предваряться нулём, иначе компилятор не сможет отличить число от идентификатора. Примеры чисел см. в разделе 2.6.

2.3. Символьные данные

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

Если в качестве символа или внутри строки надо указать апостроф или кавычку, то делается это следующим образом: если символ или строка заключены в апострофы, то апостроф надо удваивать, а кавычку удваивать не надо, и наоборот, если символ или строка заключены в двойные кавычки, то надо удваивать кавычку и не надо удваивать апостроф. Все следующие примеры корректны и эквивалентны: 'don''t', 'don»t', «don't», «don»»t».

Источник: http://natalia.appmat.ru/c%26c%2B%2B/assembler.html

Refy-free
Добавить комментарий