Работа с файлами

Assembler 8086 8088 рисование простых фигур эллипс. Программирование на ассемблере для начинающих и не только

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

Микропроцессор 8086 имеет 14 регистров. В прошлом выпуске мы встретили два из них: AH и DX. В Т аблицах N 1, 2 и 3 приведены списки всех регистров , кроме IP и регистра флагов, которые со временем будут рассматриваться отдельно:

AX

BX

CX

DX

AH BH CH DH

Таблица N 1. Регистры данных

Таблица N 3. Сегментные регистры

Регистры данных (Таблица N 1) .

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

В верхнем ряду Таблицы (AX (аккумулятор), BX (база), CX (счетчик), DX (регистр данных)) находятся шестнадцатиразрядные регистры, которые могут хранить числа от 0 до 65.535 (от 0 h до FFFFh в шестнадцатеричной системе (вспоминаем прошлый выпуск) ) . Под ним идет ряд восьмиразрядных регистров (AH, AL, BH, BL, CH, CL, DH, DL), которые могут хранить максимальное число 255 ( FFh) . Это половинки (старшая или младшая) шестнадцатиразрядных регистров.

Например:

Мы уже знаем оператор mov , который предназначен для загрузки числа в регистр. Чтобы присвоить, к примеру, регистру AL число 35 h , нам необходимо записать так:

mov al,35h

а регистру AX число 346 Ah так:

mov ax,346Ah

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

Например, следующие записи будут ошибочны:

Mov ah,123h ---> максимум FFh

Mov bx,12345h ---> максимум FFFFh

Mov dl,100h ---> максимум FFh

Здесь надо отметить, что если шестнадцатеричное число начинается не с цифры (напр.: 12 h) , а с буквы (напр.: С 5h), то перед таким числом ставится нуль : 0 C5h . Это необходимо для того, чтобы Ассемблер мог отличить где шестнадцатеричное число, а где метка. Ниже мы рассмотрим это на примере.

Допустим, мы выполнили команду mov ax,1234h . В этом случае в регистре ah будет находится число 12 h , а в регистре al 34h . Т.е. ah, al, bh, bl, ch, cl, dh и dl это младшие (L ow) или старшие (H igh) половинки шестнадцатиразрядных регистров (см. Таблицу N 4).

Таблица N 4. Результаты выполнения различных команд

Новые операторы

Рассмотрим еще два оператора: add и sub .

Оператор ADD имеет следующий формат (в последствии мы всегда будем оформлять новые команды в такие таблицы):

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

Команда ADD производит сложение двух чисел.

Примеры:

mov al,10 ---> загружаем в регистр AL число 10

add al,15 ---> al = 25; al - приемник, 15 - источник

mov ax,25000 ---> загружаем в регистр AX число 25000

add ax,10000 ---> ax = 35000; ax - приемник, 10000 - источник

mov cx,200 ---> загружаем в регистр CX число 200

mov bx,760 ---> а в регистр BX --- 760

add cx,bx ---> cx = 960, bx = 760 (bx не меняется); cx - приемник, bx - источник

Команда Перевод ( с англ.) Назначение Процессор
SUB приемник, источник Subtraction вычитание Вычитание 8086

Команда SUB производит вычитание двух чисел.

Примеры:

mov al,10

sub al,7 ---> al = 3; al - приемник, 7 - источник

mov ax,25000

sub ax,10000 ---> ax = 15000; ax - приемник, 10000 - источник

mov cx,100

mov bx,15

sub cx,bx ---> cx = 85, bx = 15 (bx не меняется); cx - приемник, bx - источник

Это интересно

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

Каждая команда процессора выполняется определенное количество тактов. Когда говорят, что тактовая частота процессора 100Mhz, то это значит, что за секунду проходит 100 миллионов тактов.

Чтобы сложить два числа в Ассемблере нужно выполнить следующие команды:

mov ax,2700

mov bx,15000

add ax,bx

В результате выполнения данных инструкций, в регистре AX будет число 17700, а в регистре BX - 15000. Команда add ax,bx выполняется за один такт на процессоре 80486. Получается, что компьютер 486 DX2-66Mhz за одну секунду сложит два числа любых числа (от 0 до 0FFFFh) 66 миллионов раз!

Регистры -указатели (Таблица N 2 ) .

Регистры SI (индекс источника) и DI (индекс приемника) используются в строковых операциях. Регистры BP и SP необходимы при работе со стеком. Мы их будем подробно рассматривать в последующих выпусках.

Сегментные регистры (Таблица N 3 ) .

Сегментные регистры необходимы для обращения к тому или иному сегменту памяти (например, видеобуферу). Сегментация памяти довольно сложная и объемная тема, которую также будем рассматривать в последующих выпусках.

Новые операторы

Команда INC увеличивает на единицу регистр. Она эквивалентна команде

ADD источник, 1

только выполняется гораздо быстрее.

Примеры:

mov al,15

inc al ---> теперь AL = 16 (эквивалентна add al,1)

mov dh,39h

inc dh ---> DH = 3Ah (эквивалентна add dh,1)

mov cl,4Fh

inc cl ---> CL = 50h (эквивалентна add cl,1)

_____________________

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

Управлять клавиатурой позволяет прерывание 16h. Это прерывание BIOS (ПЗУ), а не MS-DOS (как 21h). Его можно вызывать даже до загрузки операционной системы, в то время, как прерывание 21h доступно только после загрузки COMMAND.COM.

Чтобы остановить программу до нажатия любой клавиши следует вызвать функцию 10h прерывания 16h. Вот как это выглядит (после стрелки (--->) идет комментарий):

mov ah,10h ---> в AH всегда указывается номер функции

int 16h ---> вызываем прерывание 16h - сервис работы с клавиатурой BIOS (ПЗУ)

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

Следующая программа выводит на экран сообщение и ждет нажатия любой клавиши (равнозначна команде pause в *.bat файлах):

(1) CSEG segment

(6) mov dx,offset String

(14) String db "Нажмите любую клавишу...$"

Строки с номерами (1), (2) и (15) пока опускаем. В строках (5) - (7), как вы уже знаете, выводим на экран сообщение. Затем, строки (9) - (10), ждем нажатия клавиши. И, наконец, строка (20) выходит из нашей программы.

Мы уже знаем команды INC, ADD и SUB. Можно поэксперементировать с вызовом прерывания. Например, так:

mov ah,0Fh

inc ah

int 16h

Это позволить Вам лучше запомнить новые операторы.

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

Все внутренние регистры процессора Intel 8086 являются 16-битными:

Всего процессор содержит 12 программно-доступных регистров, а также регистр флагов (FLAGS) и указатель команд (IP).

Регистры общего назначения (РОН) AX, BX, CX и DX используются для хранения данных и выполнения различных арифметических и логических операций. Кроме того, каждый из этих регистров поделён на 2 части по 8-бит, с которыми можно работать как с 8-битными регистрами (AH, AL, BH, BL, CH, CL, DH, DL). Младшие части регистров имеют в названии букву L (от слова Low ), а старшие H (от слова High ). Некоторые команды неявно используют определённый регистр, например, CX может выполнять роль счетчика цикла.

Индексные регистры предназначены для хранения индексов при работе с массивами. SI (Source Index ) содержит индекс источника, а DI (Destination Index ) - индекс приёмника, хотя их можно использовать и как регистры общего назначения.

Регистры-указатели BP и SP используются для работы со стеком. BP (Base Pointer ) позволяет работать с переменными в стеке. Его также можно использовать в других целях. SP (Stack Pointer ) указывает на вершину стека. Он используется командами, которые работают со стеком. (Про стек я подробно расскажу в отдельной части учебного курса)

Сегментные регистры CS (Code Segment ), DS (Data Segment ), SS (Stack Segment ) и ES (Enhanced Segment ) предназначены для обеспечения сегментной адресации. Код находится в сегменте кода, данные - в сегменте данных, стек - в сегменте стека и есть еще дополнительный сегмент данных. Реальный физический адрес получется путём сдвига содержимого сегментного регистра на 4 бита влево и прибавления к нему смещения (относительного адреса внутри сегмента). Подробнее о сегментной адресации рассказывается в части 31 .

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

Указатель команд IP (Instruction Pointer ) содержит адрес команды (в сегменте кода). Напрямую изменять его содержимое нельзя, но процессор делает это сам. При выполнении обычных команд значение IP увеличивается на размер выполненной команды. Существуют также команды передачи управления, которые изменяют значение IP для осуществления переходов внутри программы.

Регистр флагов FLAGS содержит отдельные биты: флаги управления и признаки результата. Флаги управления меняют режим работы процессора:

  • D (Direction ) - флаг направления. Управляет направлением обработки строк данных: DF=0 - от младших адресов к старшим, DF=1 - от старших адресов к младшим (для специальных строковых команд).
  • I (Interrupt ) - флаг прерывания. Если значение этого бита равно 1, то прерывания разрешены, иначе - запрещены.
  • T (Trap ) - флаг трассировки. Используется отладчиком для выполнения программы по шагам.

Признаки результата устанавливаются после выполнения арифметических и логических команд:

  • S (Sign ) - знак результата, равен знаковому биту результата операции. Если равен 1, то результат - отрицательный.
  • Z (Zero ) - флаг нулевого результата. ZF=1, если результат равен нулю.
  • P (Parity ) - признак чётности результата.
  • C (Carry ) - флаг переноса. CF=1, если при сложении/вычитании возникает перенос/заём из старшего разряда. При сдвигах хранит значение выдвигаемого бита.
  • A (Auxiliary ) - флаг дополнительного переноса. Используется в операциях с упакованными двоично-десятичными числами.
  • O (Overflow ) - флаг переполнения. CF=1, если получен результат за пределами допустимого диапазона значений.

Не волнуйтесь, если что-то показалось непонятным. Из дальнейшего объяснения станет ясно, что к чему и как всем этим пользоваться 🙂

ЯЗЫК АССЕМБЛЕРА МИКРОПРОЦЕССОРА 8088

Язык ассемблера - машинный язык, записанный в символической форме. В то время как адреса и команды в машинном языке суть числа, язык ассемблера разрешает присваивать адресам памяти неповторяющиеся имена, состоящие из букв. Так как исполнительные устройства микропроцессоров 8086 и 8088 идентичны, то и язык ассемблера 8088 также оказывается иден-тичным языку ассемблера 8086 и является частью языка ассемблера микропроцессоров 80186 и 80286, представляющих собой улучшенные модификации ЦП 8086.

Программа, написанная на языке ассемблера 8088, может быть переведена (транслирована) на машинный язык с помощью программы, называемой ассемблером. Ассемблер, описанный в данной книге, является макроассемблером фирмы Microsoft, используемым в персональном компьютере IBM с дисковой операционной системой PC-DOS.

Существуют и другие версии ассемблеров, приемлемые для семейства микропроцессоров 8086/8088, например ассемблер IASM-86, разработанный фирмой Intel Corporation, который (включает стандартные мнемонические команды, в том числе команды арифметических операций для чисел с плавающей запятой в процессоре 8087. Несмотря на имеющиеся различия между этими ассемблерами, их структуры и форматы команд языков в значительной степени совместимы.

Почему необходим язык ассемблера?

Очень неудобно, утомительно, а иногда и трудно писать программы непосредственно на машинном язьь ке. Программист при этом должен выполнить массу кропотливых и рутинных операций: следить за распределением ячеек в памяти микро-ЭВМ для записи команд или программных данных, чтобы затем можно было указать или определить эти адреса в программе; помнить действительные машинные команды, представляемые в двоичном или в шестнадцатеричном коде, и связанные с ними способы адресации микро-


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

Язык ассемблера - способ символической записи программ на машинном языке. Например, код машинной команды ЗС02 (шестнадцатеричный) может быть записан в символической форме на языке ассемблера как CMP AL,02 (эта команда сравнивает содержимое регистра AL с шестнадцатеричным числом 02). Таким образом, рутинные преобразования машинных кодов и данных в программе, перечисленные выше, можно. максимально переложить на программу - ассемблер. Следовательно, основная цель ассемблера - трансляция программ, написанных на языке ассемблера, в машинный язык (машинные коды).

Ассемблер следит за использованными ячейками памяти, с тем чтобы команды в программе имели ссылки на эти адреса в символическом виде, используя имена и метки, заменяющие действительные значения. Например, машинный код в шестнадцатерич-ной записи С606001201 может быть записан на языке ассемблера как MOV BYTE-COUNT,01, где BYTE-COUNT есть символическое имя адреса памяти 1200Н.

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

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

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




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

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

Формат программы

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

Первое поле используется для задания имени или символа ячейки памяти, содержащей команду, константу или данные. Адрес этой ячейки содержится в

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

Язык ассемблер - это низкоуровневый язык программирования или же программа, которая исходный текст программы, написанный на языке ассемблера, переводит в программу на машинный язык. Язык, по некоторым меркам сложный, но ведь создание примитивов графики берет начало именно тут. Я же хочу рассмотреть ассемблер под Windows, а именно MASM , который, на ряду с Visual Studio, не так давно использовал для создания графических примитивов. Об этом с иллюстрациями и подробностями далее.

Приступая к работе
Рассмотрим маломальски простенькую структуру, которая необходима для создания приложений под Windows:
1) помещаем все константы, стpуктуpы и функции, относящиеся к Windows в начале нашего.asm файла - экономим силы и время;
2) используем диpективу includelib, чтобы указать библиотеки импоpта - это укажет компилятоpу на то, что пpогpамма будет использовать функции из этих библиотек импоpта;
3) объявляйте пpототипы API-функций, стpуктуp и/или констант в подключаемом файле с использованием тех же имен, что и в Windows include файлах, по крайней мере старайтесь, поскольку это избавит всех от головной боли в будующем;
4) используйте makefile, чтобы автоматизиpовать пpоцесс компиляции.

Я же отступлю кое-где и кое-как, но в целом у нас должна получиться отличная программа, которая нарисует нам довольно интересный таки примитив. Рассмотрим пример структуры программы на Ассемблере (см. Листинг 1)

Листинг 1. Пример структуры программы
.type_process ; описание типа процессора
.model ; описание модели памяти

Include lib ; подключение inc
includelib lib ; подключение lib

DATA ; иницилизиpуемые данные
; имя класса и окна

DATA? ; неиницилизиpуемые данные
; дескриптор пpогpаммы

CODE ; здесь начинается код программы

Определение графических примитивов
Контекст Устройства и WM_PAINT
В Windows окно само отвечает за перерисовку себя. Для того чтобы окно осуществило перерисовку, оно должно получить сообщение WM_PAINT.

Обычно используют один из трех методов:

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

Установка текущей позиции
Для установки текущей позиции используется функция MoveToEx(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI MoveToEx(HDC, int, int, LPPOINT);

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

Рисование линии
Для прорисовки линии используется функцию LineTo(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI LineTo(HDC, int, int);

Первый аргумент - контекст устройства, второй и третий аргументы - координаты точек.

Рисование прямоугольника
Для прорисовки прямоугольника используется функция Rectangle(), где функция описывается следующим способом:

WINGDIAPI BOOL WINAPI Rectangle(HDC, int, int, int, int);

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

Рисование эллипса
Для прорисовки эллипса необходимо вызвать функцию Ellipse(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI Ellipse(HDC, int, int, int, int);

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

Рисование прямоугольника с закругленными краями
Для прорисовки прямоугольника с закругленными краями используется функция RoundRect(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI RoundRect(HDC, int, int, int, int, int, int);

Первые пять аргументов полностью идентичны аргументам функции Rectangle(). Последние два аргумента содержат ширину и высоту эллипса, определяющего дуги.

Написание и разбор.asm кода

Для написания примитива рассмотрим шаги, которые необходимы для создания и отрисовки графики:
1) получение дескриптора для программы;
2) регистрация класса окна;
3) создание окна;
4) отображение окна на экpане;
5) обновление содержимого экpана в окне;
6) выход из пpогpаммы.

Приступим к созданию, но для начала создадим новый проект в Visual Studio: File -> New Project

Выбираем пустой прокт: Empty project

Создаем новый файл: правой кнопкой по Source -> Add -> New Item

Создаем новый файл (.asm):
1-ый способ - дописать при создании нового файла file.asm (я таким способом создавал)
2-ой способ - изменить расширение файлу после его создания (file.txt -> rename -> file.asm)

Используем masm в Visual Studio: нажимаем правой кнопкой по преокту -> Build Customization

Задаем этот самый masm: ставим галочку напротив masm

Приступаем к написанию этого самого примитива, а сам листинг смотрите ниже.

Листинг 2. Написание кода на ассемблере
.386
.model stdcall, flat
option casemap:none

Includelib kernel32.lib
include kernel32.inc
includelib user32.lib
include user32.inc
include windows.inc
include gdi32.inc

Hwnd dd 0
hInst dd 0
szTitleName db "АиПОС. Лабороторная работа №6", 0
szClassName db "Приложение Win32", 0
msg MONMSGSTRUCT
wc WNDCLASS
ps PAINTSTRUCT

Main PROC
invoke GetModuleHandle, 0 ;получение значения баз. адреса,
mov hInst, eax ;по которому загружен модуль.
mov wc.style, CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS
mov wc.lpfnWndProc, offset WndProc ;адрес оконной процедуры
mov wc.cbClsExtra, 0
mov wc.cbWndExtra, 0
mov eax, hInst ;дескриптор приложения
mov wc.hInstance, eax ;в поле hInstance
invoke LoadIcon, 0, IDI_APPLICATION
mov wc.hIcon, eax ;дескриптор значка в поле hIcon
invoke LoadCursorA, 0, IDC_ARROW
mov wc.hCursor, eax ;дескриптор курсора в поле hCursor
mov wc.hbrBackground, WHITE_BRUSH ;цвет бекграунда окна белый
mov dword ptr wc.lpszMenuName, 0 ;главного меню нет
mov dword ptr wc.lpszClassName, offset szClassName ;имя класса окна
invoke RegisterClassA, offset wc ;регистрация класас окна
invoke CreateWindowEx, 0, offset szClassName, offset szTitleName, \
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, \
CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInst, 0
mov hwnd, eax ;создание окна
invoke ShowWindow, hwnd, SW_SHOWNORMAL ;показ окна
invoke UpdateWindow, hwnd ;перерисовывка содержимого окна
cycle1: ;цикл сообщений
invoke GetMessage, offset msg, 0, 0, 0
cmp ax, 0
je end_c
invoke TranslateMessage, offset msg ;трансляция ввода с клавиатуры
invoke DispatchMessage, offset msg ;отправляем сообщение
;оконной процедуре
jmp cycle1
end_c:
invoke ExitProcess, 0 ;выход из приложения
Main ENDP

WndProc PROC USES ebx edi esi, _hwnd:DWORD, _wmsg:DWORD, _wparam:DWORD, _lparam:DWORD
local _hdc:DWORD
cmp _wmsg, WM_DESTROY
je wmdestroy
cmp _wmsg, WM_PAINT
je wmpaint
invoke DefWindowProcA, _hwnd, _wmsg, _wparam, _lparam ;обраб. по умолчанию
jmp exit_proc
wmpaint:
invoke BeginPaint, _hwnd, offset ps ;получаем контекст устройства
mov _hdc, eax
invoke Rectangle, _hdc, 170, 120, 310, 260 ;тело
invoke Rectangle, _hdc, 120, 120, 170, 140 ;левая лапа
invoke Rectangle, _hdc, 310, 120, 360, 140 ;правая лапа
invoke Rectangle, _hdc, 170, 260, 190, 310 ;левая ноголапа
invoke Rectangle, _hdc, 290, 260, 310, 310 ;правая ноголапа
invoke Rectangle, _hdc, 210, 80, 270, 120 ;башка
invoke Rectangle, _hdc, 220, 85, 225, 90 ;левый глаз
invoke Rectangle, _hdc, 250, 85, 255, 90 ;правый глаз
invoke Rectangle, _hdc, 225, 105, 255, 120 ;рот
invoke EndPaint, _hdc, offset ps ;освобождаем контекст

jmp exit_proc
wmdestroy:
invoke PostQuitMessage, 0 ;послать сообщение WM_QUIT
mov eax, 0 ;возвращаемое значение - 0
exit_proc:
ret
WndProc ENDP
END Main

Результат

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

Разбор полётов
Строка с.386 передает MASM, что используется набор инструкций пpоцессоpа 80386. Строка.model stdcall, flat передает MASM, что будет использоваться плоская модель памяти. А саму передачу паpаметpов использовали типом STDCALL как по умолчанию.
Подключил windows.inc в начале кода, поскольку он содеpжит системный стpуктуpы и константы, котоpые потpебовались для реализации примитивов в пpогpамме. Поскольку пpогpамма вызывает API функции Windows, которые находятся в user32.dll (CreateWindowEx и другие) и kernel32.dll (ExitPocess и другие) их необходимо тоже прописать.
Описываем прототип главной функции PROC.
Следом идёт.data, где: szClassName - имя нашего класса окна и szTitleName - имя нашего окна.
В.code содеpжит все инстpукции, где код должен pасполагаться между <имя метки> и end <имя метки>.
Пеpвая же инстpукция - вызов GetModuleHandle, чтобы получить дескриптор нашей пpогpаммы. Она используется как паpаметp, пеpедаваемый функциям API, которые вызываются нашей пpогpаммой.

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

1) cbSize: задает размеp общей стpуктуpы WDNCLASSEX в байтах;
2) style: задает стиль окона;
3) cbClsExtra: задается количество дополнительных байтов, котоpые нужно будет зарезервировать для самой программы;
4) hInstance: задает дескриптор модуля;
5) hIcon: задает дескриптор иконки, а его получение просходит посредством обращения функции LoadIcon;
6) hCursor: задает дескриптор куpсоpа, а его получение просходит посредством обращения функции LoadCursor;
7) hbrBackground: задает цвет фона;
8) lpszMenuName: задается дескриптор меню для окон;
9) lpszClassName: задается имя класса окна.

После pегистpации класса окна функцией RegisterClassEx, происходит вызов CreateWindowEx, чтобы создать наше окно, основанное на этом класе.

Основной и немаловажной является процедура WndProc PROC USES ebx edi esi, _hwnd:DWORD, _wmsg:DWORD, _wparam:DWORD, _lparam:DWORD.Не обязательно ее было называть ее WndProc, где пеpвый паpаметp, _hwnd - это хэндл окна, котоpому пpедназначается сообщение,_wmsg - передаваемое сообщение. Стоит сказать, что _wmsg - это не msg стpуктуpа, но это всего лишь число. _wparam и _lparam - это дополнительные паpаметpы, которые используются некоторыми сообщениями.

В конце концов подошли к заключительной части, где и описываются задаваемые фигуры, их координаты и возвращаемые значения. Это ключевая часть, поскольку именно здесь pасполагается логика действий пpогpаммы. Тут же описываем освобождение контекста и возравщаем значения, где далее посылаем сообщение о завершении. Единственное сообщение, которое осталось обработать - wmdestroy - это сообщение будет посылаться окну, когда оно закpывается. В то вpемя, когда пpоцедуpа окна его получает, окно уже исчезло с экpана. После выполнения wmdestroy вызывается PostQuitMessage, котоpый посылает сообщение о выходе и это вынуждает GetMessage веpнуть нулевое значение в eax, а это уже выход из программы.

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

Первым промышленным 8-битовым микропроцессором (обрабатывавшим одно­временно 8 битов информации) стал микропроцессор 8008, выпущенный фирмой Intel в 1972 году. Он считается лучшим 8-битовым микропроцессором первого поко­ления. По своей архитектуре этот микропроцессор похож на калькулятор; он имеет аккумулятор, шесть регистров общего назначения, указатель стека (специальный регистр адреса рабочих ячеек), восемь регистров адреса и специальные команды для ввода и вывода данных. В 1973 году фирма Intel выпустила версию второго поколения-микропроцессора 8008, получившую название 8080.

По сравнению с микропроцессором 8008 микропроцессор 8080 мог адресоваться к большей памяти, имел расширенные возможности ввода-вывода, обладал допол­нительными командами и работал быстрее. Хотя идеи архитектуры микропроцес­сора 8008 были в основном перенесены фирмой Intel и на микропроцессор 8080, его внутренняя организация была улучшена настолько, что микропроцессор 8080 стал стандартом de facto для микропроцессоров второго поколения; и когда речь заходит о микропроцессорах, то многим людям первым делом приходит на ум именно микропроцессор 8080.

Достижения технологии позволили фирме Intel в 1976 году выпустить усовер­шенствованную версию микропроцессора 8080, названную 8085. Он отличался от микропроцессора 8080 конструкцией корпуса, имел сброс в начальное состояние (для инициализации микропроцессора), прерывания по вектору (для обслужива­ния периферийных устройств), последовательный порт ввода-вывода (для подклю­чения принтеров и других периферийных устройств). Кроме того, ему требовался только один источник питания +5 В (микропроцессору 8080 требовалось два источ­ника питания).

Ко времени выпуска микропроцессора 8085 фирма Intel встретилась с серьезной конкуренцией на рынке 8-битовых микропроцессоров. Начали пользоваться успехом микропроцессор Z80 фирмы Zilog Corporation, представляющий собой усовершенствование микропроцессора 8080, а также микропроцессоры 6800 фирмы Motorola и 6502 фирмы MOS Technology (ныне фирма Commodore), существенно отличавшиеся от 8080 по своей архитектуре. И вместо того, чтобы продолжать борьбу на переполненном рынке 8-битовых микропроцессоров, фирма Intel сделала качественный шаг вперед и в 1978 году выпустила 16-битовый микропроцессор 8086, который мог обрабатывать данные в 10 раз быстрее, чем микропроцессор 8080.

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

Так как многие проектировщики все еще предпочитали пользоваться в своих 16-битовых системах более дешевыми 8-битовыми вспомогательными и перифе­рийными микросхемами, то фирма Intel выпустила версию микропроцессора 8086, имевшую то же устройство внутри, но 8-битовую Шину данных снаружи. Эта версия (микропроцессор 8088) идентична 8086, но затрачивает больше времени на переда­чи 16-битовых данных, которые выполняются с помощью двух последовательных 8-битовых передач. Однако в приложениях, где обрабатываются в основном 8-бито­вые значения, микропроцессор 8088 отстает по производительности от микропро* цессора 8086 не более чем на 10%.

Таким образом, Вы можете считать, что Ваша персональная ЭВМ фирмы IBM имеет 16-битовый микропроцессор. (И, следовательно, можете пользоваться много­численной литературой по микропроцессору 8086.) Завершим на этом введение и перейдем к обсуждению возможностей микропроцессора 8088.