Форум программистов, компьютерный форум, киберфорум
Наши страницы

Assembler, MASM, TASM

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 1, средняя оценка - 5.00
Mikl___
Заблокирован
Автор FAQ
#1

Сам себе Iczelion - Assembler

02.01.2013, 15:15. Просмотров 63926. Ответов 116
Метки нет (Все метки)

Содержание

20
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
02.01.2013, 15:15
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Сам себе Iczelion (Assembler):

создать програму которая содержит в себе команды обработки строк языка асемблер - Assembler
Создать програму которая содержит в себе команды обработки строк языка асемблер. Выполнить введение строки из 40 символов. Слова в строке...

Регистры eax & edx сами по себе принимают непонятные значения - Assembler
Задача программы сортировка массива состоящего из 5ти двойных слов. Ассемблер конечно сортирует, но только максимум 3 первых числа, а 2...

Создать программу, которая содержит в себе элементы цикла и разветвления - Assembler
в массиве из n=10 элементов найти наибольшее число

Выключается сам по себе - Компьютерное железо
После очистки компа от пыли (снимал кулер, проц, оперативку и видюху, так же менял термопасту). Я нажимаю на кнопку Power, происходит...

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

Вырубается ПК сам по себе - Компьютерное железо
Нужна помощь в проблеме. Пару недель назад кулер в БП сильно шумел время от времени, я решил почистить пк. Почистил ПК от пыли полностью,...

116
Mikl___
Заблокирован
Автор FAQ
02.01.2013, 21:50  [ТС] #2
Бредисловие

Режисер ― Не пора ли, друзья мои,
нам замахнутся на Уильяма,
понимаете ли, нашего Шекспира?
Голос из зала ― И замахнемся!

Из кинофильма «Берегись автомобиля»)
Не сочтите за кощунство. Но попробую переписать уроки Iczelion'а с позиции уменьшения размера. Писалось под WinXP и Seven на масме, а позже было переведено на FASM, NASM и GoAsm.
В онлайновом учебнике по программированию на ассемблере в win32 (перевод UniSoft) есть такие строки: «Я провел эксперимент, написал программу (обычное окно с одной кнопкой в центре, которая закрывает его) на разных языках высокого уровня и после компиляции получил вот такие размеры этой самой программы:
Кликните здесь для просмотра всего текста
программаразмер
C++ Builder 422 kb
Delphi 5291 kb
Delphi 5 + библиотека KOL26 kb
Ассемблер MASMоколо 3 kb
Большинство EXE-файлов, созданных в уроках Iczelion'а, имеют размер около 4к, я уменьшаю их размер до 1к и менее, наименьший размер, к которому можно/нужно стремиться 97 байт, но это PE-заголовок файла перемешанный с кодом, это уже «ручная выделка»... я пока о стандартных методах уменьшения ЕХЕ файлов, работающих под Win XP и seven.
Русский перевод "Уроков Iczelion'a" взят на сайте WASM.RU, там же в разные годы публиковались и комментарии к "Урокам..."
  • © Iczelion (автор туториалов). См. http://win32assembly.programminghorizon.com/index.html
  • © Aquila (перевод на русский). См. http://www.wasm.ru
  • © SheSan (перевод на русский). Раздел ODBC Tutorial.
  • © WD-40 (перевод на русский).Win32 API. Урок 23, 25.
  • © N/A (перевод на русский).Win32 API. Урок 33.


По версии журнала "Хакер" #45 2002 Iczelion выглядит вот так:
Отрывки из статьи TanaT (TanaT@hotmail.ru) «Ассемблер: свежий взгляд. Что думают об этом профессионалы?»

... мы обратились за советом к мастеру, которого все знают под ником Iczelion...

Для программирования под Windows нет ничего лучшего, чем туториалы, написанные Iczelion'ом. Он потратил целых два года на то, чтобы любой (даже начинающий!) кодер смог почти сразу въехать в программирование на ассемблере под Винду. Его основной девиз: «Кодить под Винду на асме не сложнее, чем на С». Вся документация Iczelion'a разбита на уроки, изучать которые надо, естественно, по порядку...

Личное дело: Iczelion. Родился в 1966. Живет в Таиланде. Работает на правительство. Ассемблер ― первый язык, который он выучил после окончания университета. В книге «Руководство для IBM/PC и PS/2» Питера Нортона в разделе о языках программирования, Iczelion нашел строки: «Только язык ассемблера позволяет использовать мощность вашего ПК в полном объеме...». Эти слова заложили начало длинного пути, в результате которого появился блестящий эксперт по низкоуровневому программированию.

Домашняя страничка Iczelion'а ― http://win32asm.cjb.net.

TanaT ― Некоторые люди считают, что ассемблер мертв. Что скажешь?
Iczelion ― Для некоторых он действительно мертв: они не хотят его использовать, а он ничем не может быть им полезен. Но есть люди, для которых ассемблер является средством существования. Например, VX-кодеры. Для многих людей этот язык просто близок собственному мыслительному процессу.
TanaT ― Нужны ли сегодня программисты на ассемблере?
Iczelion ― Да. Но не в таких количествах, как, скажем, ASP или С++ кодеры. Но сегодня человек должен быть очень хорошо подготовлен, чтобы его приняли на работу.
TanaT ― Если человек знает ассемблер, он может найти себе хорошую работу?
Iczelion ― Я бы на это не рассчитывал. Ассемблер это всего лишь один из языков программирования. У него есть свои сильные и слабые стороны. Что касается работы, то это сильно зависит от человека и самой организации, которой требуются асм-кодеры.
TanaT ― Спасибо, этот вопрос разъяснили. Что бы ты посоветовал нашим читателям, уже знающим ассемблер или только собирающимся его изучать?
Iczelion ― Если вы хотите изучать ассемблер, сперва спросите себя, зачем он вам. Не учите его лишь потому, что он вам кажется «элитным». Ни один из языков программирования не является лучшим для написания абсолютно любых приложений. Сам я пишу на ассемблере далеко не все. Чаще всего я использую VBA или VBScript, если они позволяют решить задачу без потери производительности. А на ассемблере я пишу потому, что люблю этот язык и он мне кажется «естественным». Программирование на ассемблере требует большой концентрации, так как каждая инструкция делает очень мало: только если несколько их собрать воедино, получится какой-нибудь результат. Если вы невнимательны, подумайте еще раз. Программирование на ассемблере требует больших затрат времени, даже если вы используете структурное программирование. То есть программы на ассемблере это чаще всего маленькие утилиты. Я не хочу совсем отпугнуть новичков, просто излагаю некоторые факты самого языка, чтобы вы могли принять объективное решение...


© Mikl___ 2013

Win32 API. Урок 1. Основы
Этот Урок предполагает, что читатель знает, как использовать MASM. Если вы не знакомы с MASM, скачайте c masm32.com и прочитайте текст, входящий в состав пакета, прежде чем продолжать чтение этого введения. Хорошо. Теперь вы готовы. Давайте приступим.
ТЕОРИЯ ― МАТЬ СКЛЕРОЗА
Win32 программы выполняются в защищенном режиме, который доступен начиная с 80286. Hо 80286 теперь история. Поэтому мы предполагаем, что имеем дело только с 80386 и его потомками. Windows запускает каждую Win32 программу в отдельном виртуальном пространстве. Это означает, что каждая Win32 программа будет иметь 4-х гигабайтовое адресное пространство.
Hо это вовсе не означает, что каждая программа имеет 4 гигабайта физической памяти, а только то, что программа может обращаться по любому адресу в этих пределах. Windows сделает все необходимое, чтобы сделать память, к которой программа обращается "существующей". Конечно, программа должна придерживаться правил, установленных Windows, или это вызовет General protection Fault.

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

Модель памяти также коренным образом отличается от существующих в старом мире 16-битных программ. Под Win32, мы больше не должны беспокоиться о моделях памяти или сегментах! Теперь только одна модель память: Плоская модель памяти. Теперь нет больше 64K сегментов. Память теперь это большое последовательное 4-х гигабайтовое пространство. Это также означает, что вы не должны "играть" с сегментными регистрами. Вы можете использовать любой сегментный регистр для адресации к любой точке памяти. Это ОГРОМНОЕ подспорье для программистов. Это то, что делает программирование на ассемблере под Win32 таким же простым, как на C.

Когда вы программируете под Win32, вы должны помнить несколько важных правил.
Одно из таких правил то, что Windows использует esi, edi, ebp и ebx внутренне и не ожидает, что значение в этих регистрах меняются. Так что помните это правило: если вы используете какой-либо из этих четырех регистров в вызываемой функции, не забудьте восстановить их перед возвращением управления Windows.
Вызываемая (callback) функция - это функция, которая вызывается Windows.
Очевидный пример - процедура окна. Это не значит, что вы не можете использовать эти четыре регистра. Просто не забудьте восстановить их значения перед передачей управления Windows.
ПРАКТИКА ― МАТЬ ШИЗОФРЕНИИ
Вот каркасная программа. Если что-то из кода вы не понимаете, не паникуйте. В дальнейшем я все объясню.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.386
.MODEL Flat, STDCALL
.DATA
   <Ваша инициализируемые данные>
   ......
.DATA?
   <Ваши не инициализируемые данные>
   ......
.CONST
   <Ваши константы>
   ......
.CODE
<метка>:
   <Ваш код>
   ......
end <метка>
Вот и все! Давайте проанализируем этот "каркас".
Assembler
1
.386
Это ассемблерная директива, говорящая ассемблеру использовать набор операций для процессора 80386. Вы также можете использовать .486, .586, .686 но самый безопасный выбор ― это указывать .386. Также есть два практически идентичных выбора для каждого варианта CPU. .386/.386p, .486/.486p. Эти "p"-версии необходимы только тогда, когда ваша программа использует привилегированные инструкции, то есть инструкции, зарезервированные процессором/операционной системой для работы в защищенном режиме. Они могут быть использованы только в защищенном коде, например, sys-драйверами. Как правило, ваши программы будут работать в непривилегированном режиме, так что лучше использовать не-"p" версии.
Assembler
1
.MODEL FLAT, STDCALL
.MODEL ― ассемблерная директива, определяющая модель памяти вашей программы. Под Win32 есть только одна ― плоская модель.
STDCALL говорит MASM'у о порядке передачи параметров, слева направо или справа налево, а также о том, кто уравнивает стек, после того как функция вызвана.
Под Win16 существует два типа передачи параметров, C и PASCAL. По C-договоренности, параметры передаются справа налево, то есть самый правый параметр кладется в стек первым. Вызывающий должен уравнять стек после вызова. Например, при вызове функции с именем foo(int first_param, int second_param, int third_param), используя C-передачу параметров, ассемблерный код будет выглядеть так:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
push [third_param]  ; Положить в стек третий параметр
push [second_param] ; Следом - второй
push [first_param]  ; И, наконец, первый
call foo
add  esp, 12         ; Вызывающий уравнивает стек

PASCAL-передача параметров ― это C-передача наоборот. Согласно ей, параметры передаются слева направо и вызываемый параметр должен уравнивать стек.
Win16 использует этот порядок передачи данных, потому что тогда код программы становится меньше. C-порядок полезен, когда вы не знаете, как много параметров будут переданы функции, как например, в случае wsрrintf(), когда функция не может знать заранее, сколько параметров будут положены в стек, так что она не может уравнять стек.
STDCALL - это гибрид C и PASCAL вызовов. Согласно ему, данные передаются справа налево, но вызываемая функция ответственна за очистку стека от переданных ей параметров. Платформа Win32 использует исключительно STDCALL, хотя есть одно исключение -- функция wsprintf(). Вы должны следовать C-порядку вызова в случае wsprintf().
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
.DATA
 
.DATA?
 
.CONST
 
.CODE
Все четыре директивы это то, что называется секциями. Вы помните, что в Win32 нет сегментов? Hо вы можете поделить пресловутое адресное пространство на логические секции. Начало одной секции отмечает конец предыдущей. Есть две группы секций: данных и кода.
.DATA ― Эта секция содержит инициализированные данные вашей программы.
.DATA? ― эта секция содержит неинициализированные данные вашей программы. Иногда вам нужно только "предварительно" выделить некоторое количество памяти, но вы не хотите инициализировать ее. Эта секция для этого и предназначается. Преимущество неинициализированных данных следующее: они не занимают места в исполняемом файле. Например, если вы хотите выделить 10000 байт в вашей .DATA? секции, ваш exe-файл не увеличится на 10kb. Его размер останется таким же. Вы, всего лишь, говорите компилятору, сколько места вам нужно, когда программа загрузится в память.

.CONST ― эта секция содержит объявления констант, используемых программой. Константы не могут быть изменены ей. Это всего лишь "константы".
Вы не обязаны задействовать все три секции. Объявляйте только те, которые хотите использовать.
Есть только одна секция для кода: .CODE, там где содержится весь код.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
<метка>:
.....
end <метка>
где <метка> ― любая произвольная метка, устанавливающая границы кода. Обе метки должны быть идентичны. Весь код должен располагаться между
Assembler
1
<метка>
и
Assembler
1
end <метка>


© Iczelion, пер. Aquila

Win32 API. Урок 2. MessageBox
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
18
Миниатюры
Сам себе Iczelion  
Mikl___
Заблокирован
Автор FAQ
02.01.2013, 22:00  [ТС] #3
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, указанной после директивы end. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Например, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.
Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.


© Iczelion, пер. Aquila.

Урок 2a. Как уменьшить размер программы, выводящей MessageBox
Не смотря на уверение Iczelion'a, что программа должна получиться размером 1536 байт, я получил ехе-файл в 2560 байт. При просмотре внутренностей получившегося ехе-файла программой hiew32 видно, что 90% содержимого нули. Возникает законный вопрос, а как уменьшить размер программы, но чтобы при этом не терялась ее функциональность?
Шаг первый — уменьшение размера выравнивания сегментов
  • если собирать программу именно так, как об этом рассказано в Уроке 2, то при линковании не упоминается о ключе /ALIGN:размер — ключ /ALIGN[MENT] — указывает компоновщику на необходимость выравнивания сегментов в исполняемом файле на границу, кратную значению размер. Здесь размер ― это число равное степени двойки (20=1, 21=2, 22=4, 23=8, ..., 29=512 до 64К включительно).
    Если об этом ключе не упоминать, то выравнивание равно 512 байт для совместимости с программами созданными для Windows 95/98. Посмотреть значение выравнивания можно при помощи любимого редактора бинарных файлов hiew32 в заголовке программы в поле «File alignment» (Открываем наш exe программой hiew32, нажимаем F4 (Mode) и выбираем Hex, нажимаем F8 (Header) в поле «File alignment» видим число 200h=512).
Кликните здесь для просмотра всего текста
Count of sections 3
Machine Intel386
Symbol table 00000000[00000000]
Wed Sep 14 14:30:19 2005
Size of optional header 00E0
Magic optional header 010B
Linker version 5.12
OS version 4.00
Image version 0.00
Subsystem version 4.00
Entry point 00001000
Size of code 00000200
Size of init data 00000400
Size of uninit data 00000000
Size of image 00004000
Size of header 00000400
Base of code 00001000
Base of data 00002000
Image base 00400000
Subsystem GUI
Section alignment 00001000
File alignment 00000200
Stack 0010000/00001000
Heap 00100000/00001000
Checksum 00000000
Number of dirs 16
Кроме ключа /ALIGN добавим в файл makeit.bat еще некоторые ключи компиляции и линковки, которые позволят нам уменьшить количество служебной информации в asm-файлах.
  • Добавление ключа компиляции /Cp — «сохранить регистр символов во всех идентификаторах» позволяет не писать каждый раз в тексте ассемблерного файла «option casemap:none».
  • Добавление ключа компиляции /I:путь — «установить путь для включаемых файлов» позволяет нам не указывать каждый раз полный путь к inc-файлам.
20
Вложения
Тип файла: zip tut02.zip (1.6 Кб, 405 просмотров)
Mikl___
Заблокирован
Автор FAQ
03.01.2013, 07:15  [ТС] #4
Win32 API. Урок 2. MessageBox
В этом Уроке мы создадим полнофункциональную Windows программу, которое выводит сообщение — "Win32 assembly is great!".
Скачайте пример здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows предоставляет огромное количество ресурсов Windows-программам через Windows API (Application Programming Interface). Windows API — это большая коллекция очень полезных функций, располагающихся непосредственно в операционной системе и готовых для использования программами. Эти функции находятся в нескольких динамически подгружаемых библиотеках (DLLs), таких как kernel32.dll, user32.dll и gdi32.dll. Kernel32.dll содержит API-функции, взаимодействующие с памятью и управляющие процессами. User32.dll контролирует пользовательский интерфейс. Gdi32.dll ответственен за графические операции. Кроме этих трех "основных", существуют также другие dll, которые вы можете использовать, при условии, что вы обладаете достаточным количеством информации о нужных API-функциях. Windows программы динамически подсоединяется к этим библиотекам, то есть код API-функций не включается в исполняемый файл. Информация находится в библиотеках импорта. Вы должны слинковать ваши программы с правильными библиотеками импорта, иначе они не смогут найти эти функции. Когда Windows программа загружается в память, Windows читает информацию, сохраненную в программе. Эта информация включает имена функций, которые программа использует и DLL-ей, в которых эти функции располагаются. Когда Windows находит подобную информацию в программе, она вызывает библиотеки и исправляет в программе вызовы этих функций, так что контроль всегда будет передаваться по правильному адресу.
Существует две категории API функций: одни работают с ANSI-строками, а другие с Unicode-строками. Имена API-функций использующих ANSI-строки заканчиваются на "A", например, MessageBoxA. В конце имен функций для Unicode находится "W". Windows 95/98 от природы поддерживают ANSI, а Windows NT и производные от нее 2k/XP/Vista поддерживают Unicode. Обычно мы имеем дело с ANSI строками (массивы символов, оканчивающиеся NULL-ом. размер ANSI-символа — 1 байт. В то время как ANSI достаточна для европейских языков, она не поддерживает некоторые восточные языки, в которых есть несколько тысяч уникальных символов. Вот в этих случаях в дело вступает UniCode. размер символа UNICODE — 2 байта, и поэтому может поддерживать 65536 различных символов. Hо по большей части, вы будете использовать include-файл, который может определить и выбрать подходящую для вашей платформы функцию. Просто обращайтесь к именам API-функций без постфикса.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Я приведу голый скелет программы ниже. Позже мы разберем его.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.386
.model flat, stdcall
.data
.code
start:
end start
Выполнение начинается с первой инструкции, следующей за меткой, установленной после конца директив. В вышеприведенном каркасе выполнение начинается непосредственно после метки 'start'. Будут последовательно выполняться инструкция за инструкцией, пока не встретится операция передачи управления, такая как: jmр, jne, je, ret и так далее. Эти инструкции перенаправляют поток выполнения другим инструкциям. Когда программа выходит в Windows, ей следует вызвать API-функцию ExitProcess.
Assembler
1
ExitProcess proto uExitCode:DWORD
Строка выше называется прототипом функции. Прототип функции указывает ассемблеру/линкеру атрибуты функции, чтобы он сделал проверку типов данных и количество атрибутов передаваемых функции. Формат прототипа функции следующий:
Assembler
1
ИмяФункции PROTO [ИмяПараметра]:ТипДанных,[ИмяПараметра]:ТипДанных,...
Короче говоря, за именем функции следует ключевое слово PROTO, а затем список переменных с типом данных, разделенных запятыми. В приведенном выше примере с ExitProcess, эта функция была определена как принимающая только один параметр типа DWORD. Прототипы функций очень полезны, когда вы используете высокоуровневый синтаксический вызов — invoke. Вы можете считать об invoke как обычный вызов с проверкой типов данных. Например, если вы напишите:
Assembler
1
call ExitProcess
Линкер уведомит вас, что вы забыли положит в стек двойное слово. Я рекомендую вам использовать invoke вместо простого вызова. Синтаксис invoke следующий:
Assembler
1
invoke выражение [, аргументы]
Выражение может быть именем функции или указателем на функцию. Параметры функции разделены запятыми.
Большинство прототипов для API-функций содержатся в include-файлах. Если вы используете hutch'евский MASM32, они будут находится в директории MASM32/INCLUDE. Файлы подключения имеют расширение .inc и прототипы функций DLL находятся в .inc файле с таким же именем, как и у этой DLL.
Hапример, ExitProcess экспортируется из kernel32.lib, так что прототип ExitProcess находится в kernel32.inc.

Вы также можете создать прототипы для ваших собственных функций. Во всех моих экземплярах я использую hutch'евский windows.inc, который вы можете скачать с http://win32asm.cjb.net
Возвращаясь к ExitProcess: параметр uExitCode - это значение, которое программа вернет Windows после окончания программы. Вы можете вызвать функцию ExitProcess так:
Assembler
1
invoke ExitProcess, 0
Поместив эту строку непосредственно после стартовой метки, вы получите Win32-программу, немедленно выходящую в Windows, но тем не менее полнофункциональную.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:      invoke ExitProcess, 0
end start
oрtion casemaр:none говорит MASM сделать метки "чувствительными" к регистрам, то есть ExitProcess и exitprocess — это различные имена. Отметьте новую директиву — include. После нее следует имя файла, который вы хотите вставить в то место, где эта директива располагается. В примере выше, когда MASM обрабатывает линию include \masm32\include\windows.inc, он открывает windows.inc, находящийся в директории \MASM32\INCLUDE, и далее анализирует содержимое windows.inc так, как будто вы "вклеили" подключаемый файл. Хатчевский windows.inc содержит в себе определения констант и структур, которые вам могут понадобиться для программирования под Win32. Этот файл не содержит в себе прототипов функций. Windows.inc ни в коем случае не является исчерпывающим и всеобъемлющим. Hutch и я пытаемся заполнить его как можно большим количеством констант и структур, но есть еще довольно, что следовало бы включить. Он постоянно обновляется. Заходите на хатчевскую и мою странички за свежими апдейтами. Из windows.inc, ваша программа будет брать определения констант и структур. Что касается прототипов функций, вы должны подключить другие include-файлы. Они находятся в директории \masm32\include.

В вышеприведенном примере, мы вызываем функцию, экспортированную из kernel32.dll, для чего мы должны подключить прототипы функций из kernel32.dll. Этот файл - kernel32.inc. Если вы открываете его текстовым редактором, вы увидите, что он состоит из прототипов функций из соответствующей dll. Если вы не подключите kernel32.inc, вы все еще можете вызвать ExitProcess, но уже с помощью ассемблерной команды call. Вы не сможете вызвать эту функцию с помощью invoke. Дело вот в чем: для того, чтобы вызвать функцию через invoke, вы должны поместить в исходном коде ее прототип. В примере выше, если вы не подключите kernel32.inc, вы можете определить прототип для ExitProcess где-нибудь до вызова этой функции и это будет работать. Файлы подключения нужны для того, что избавить вас от лишней работы и вам не пришлось набирать все прототипы самим.
Теперь мы встречаем новую директиву — includelib. Она работает не так, как include. Это всего лишь способ сказать ассемблеру какие библиотеки использует ваша программа должна прилинковать. Хотя вы вовсе не обязаны использовать именно этот метод. Вы можете указать имена библиотек импорта к командной строке при запуске линкера, но поверьте мне, это весьма скучно и утомительно, да и командная строка может вместить максимум 128 символов.
Теперь возьмите весь исходный текст примера этого Урока, сохраните его как msgbox.asm и ассемблируйте его так:
Код
ml /c /coff /Cp msgbox.asm
/c говорит MASM'у создать .obj файл в формате COFF. MASM использует вариант COFF (Common Object File Format), использующийся под Unix, как его собственный объектный и исполняемый формат файлов.
/Cр говорит MASM'у сохранять регистр имен, заданных пользователем. Если вы используете hutch'евский MASM32 пакет, вы можете вставить "option casemaр:none" в начале вашего исходника, сразу после директивы .model, чтобы добиться того же эффекта.
После успешной компиляции msgbox.asm, вы получите msgbox.obj. Это объектный файл, от которого один шаг до екзешника. Obj содержит инструкции/данные в двоичной форме. Отсутствуют только необходимая корректировка адресов, которая проводится линкером.
Теперь сделайте следующее:
Код
link /SUBSYSTEM:WINDOWS  /LIBPATH:c:\masm32\lib  msgbox.obj
/SUBSYSTEM:WINDOWS информирует линкер о том, какого вида является будущий исполняемый модуль.
/LIBPATH:<путь к библиотекам импорта> говорит линкеру, где находятся библиотеки импорта. Если вы используете MASM32, они будут в MASM32\lib.

Линкер читает объектный файл и корректирует его, используя адреса, взятые из библиотек импорта. После окончания линковки вы получите файл msgbox.exe. Запустите его. Вы увидите, что она ничего не делает.

Да, мы не поместили в код ничего не интересного. Hо тем не менее полноценная Windows программа. И посмотрите на размер! Hа моем PC — 1.536 байт.
Теперь мы готовы создать окно с сообщением. Прототип функции, которая нам для этого необходима следующая:
Assembler
1
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd — это хэндл родительского окна. Вы можете считать хэндл числом, представляющим окно, к которому вы обращаетесь. Его значение для вас не важно. Вы только должны знать, что оно представляет окно. Когда вы захотите сделать что-нибудь с окном, вы должны обратиться к нему, используя его хэндл.
lрText — это указатель на текст, который вы хотите отобразить в клиентской части окна сообщения. Указатель ― это адрес чего-либо. Указатель на текстовую строку = адрес этой строки.
lpCaption — это указатель на заголовок окна сообщения.
uType — устанавливает иконку, число и вид кнопок окна.

Давайте изменим msgbox.asm для отображения сообщения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText     db "Win32 Assembly is Great!",0
.code
start: invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Скомпилируйте и запустите. Вы увидите окошко с сообщением "Win32 Assembly is great!".
Давайте снова взглянем на исходник.
Мы определили две оканчивающиеся NULL'ом строки в секции .data. Помните, что каждая ANSI строка в Windows должна оканчиваться NULL'ом (0 в шестнадцатеричной системе). Мы используем две константы, NULL и MB_OK. Эти константы прописаны в windows.inc, так что вы можете обратиться к ним, указав их имя, а не значение. Это улучшает читабельность кода.
Оператор addr используется для передачи адреса метки (и не только) функции. Он действителен только в контексте директивы invoke. Вы не можете использовать его, чтобы присвоить адрес метки регистру или переменной, например. В данном примере вы можете использовать offset вместо addr. Тем не менее, есть некоторые различия между ними.
1. addr не может быть использован с метками, которые определены впереди, а offset может. Например, если метка определена где-то дальше в коде, чем строка с invoke, addr не будет работать.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption  db "Iczelion Tutorial #2",0
MsgBoxText       db "Win32 Assembly is Great!",0
MASM доложит об ошибке. Если вы используете offset вместо addr, MASM без проблем скомпилирует указанный отрывок кода.
2. Addr поддерживает локальные переменные, в то время как offset нет.
Локальная переменная — это всего лишь зарезервированное место в стеке. Вы только знаете его адрес во время выполнения программы. Offset интерпретируется во время компиляции ассемблером, поэтому неудивительно, что он не поддерживает локальные переменные. Addr же работает с ними, потому что ассемблер сначала проверяет ― глобальная переменная или локальная. Если она глобальная, он помещает адрес этой переменной в объектный файл. В этом случае оператор работает как offset. Если это локальная переменная, компилятор генерирует следующую последовательность инструкций, перед тем как будет вызвана функция:
Assembler
1
2
lea eax, LocalVar
push eax
Учитывая, что lea может определить адрес метки в "рантайме", все работает прекрасно.
____________________________________________
© Iczelion, пер. Aquila.


Урок 2a. Как уменьшить размер программы, выводящей MessageBox
Не смотря на уверение Iczelion'a, что программа должна получиться размером 1536 байт, я получил ехе-файл в 2560 байт. При просмотре внутренностей получившегося ехе-файла программой hiew32 видно, что 90% содержимого нули. Возникает законный вопрос, а как уменьшить размер программы, но чтобы при этом не терялась ее функциональность?
Шаг первый — уменьшение размера выравнивания сегментов
  • если собирать программу именно так, как об этом рассказано в Уроке 2, то при линковании не упоминается о ключе /ALIGN:размер — ключ /ALIGN[MENT] — указывает компоновщику на необходимость выравнивания сегментов в исполняемом файле на границу, кратную значению размер. Здесь размер ― это число равное степени двойки (20=1, 21=2, 22=4, 23=8, ..., 29=512 до 64К включительно).
    Если об этом ключе не упоминать, то выравнивание равно 512 байт для совместимости с программами созданными для Windows 95/98. Посмотреть значение выравнивания можно при помощи любимого редактора бинарных файлов hiew32 в заголовке программы в поле «File alignment» (Открываем наш exe программой hiew32, нажимаем F4 (Mode) и выбираем Hex, нажимаем F8 (Header) в поле «File alignment» видим число 200h=512).
Кликните здесь для просмотра всего текста
Count of sections 3
Machine Intel386
Symbol table 00000000[00000000]
Wed Sep 14 14:30:19 2005
Size of optional header 00E0
Magic optional header 010B
Linker version 5.12
OS version 4.00
Image version 0.00
Subsystem version 4.00
Entry point 00001000
Size of code 00000200
Size of init data 00000400
Size of uninit data 00000000
Size of image 00004000
Size of header 00000400
Base of code 00001000
Base of data 00002000
Image base 00400000
Subsystem GUI
Section alignment 00001000
File alignment 00000200
Stack 0010000/00001000
Heap 00100000/00001000
Checksum 00000000
Number of dirs 16
Кроме ключа /ALIGN добавим в файл makeit.bat еще некоторые ключи компиляции и линковки, которые позволят нам уменьшить количество служебной информации в asm-файлах.
  • Добавление ключа компиляции /Cp — «сохранить регистр символов во всех идентификаторах» позволяет не писать каждый раз в тексте ассемблерного файла «option casemap:none».
  • Добавление ключа компиляции /I:путь — «установить путь для включаемых файлов» позволяет нам не указывать каждый раз полный путь к inc-файлам.
  • Добавление ключа компиляции /Gz — «Определить соглашение о именах и стиле вызова функций stdcall» позволяет сократить надпись «.model flat, stdcall» до «.model flat».
  • Ключ /LIBPATH:путь говорит линкеру, где находятся библиотеки импорта, что позволяет нам не указывать каждый раз полный путь к lib-файлам.
Так выглядит содержимое файла makeit.bat до изменений:
Кликните здесь для просмотра всего текста
Код
\masm32\bin\ml /c /coff "msgbox.asm"
\masm32\bin\Link /SUBSYSTEM:WINDOWS "msgbox.obj"
а так после:
Кликните здесь для просмотра всего текста
Код
\masm32\bin\ml /c /Cp /Gz /I\masm32\include /coff "msgbox.asm"
\masm32\bin\Link /SUBSYSTEM:WINDOWS /ALIGN:256 /LIBPATH:\masm32\lib "msgbox.obj"
при компиляции получаем сообщение
LINK : warring LNK4108: ALIGN specified without /DRIVER; image may not run
размер файла msgbox.exe меняется с 2560 на обещанные 1536 байт и, не смотря, на предупреждение image may not run файл msgbox.exe благополучно запускается
Продолжаем эксперимент, изменяя значение ALIGN, наблюдаем за уменьшением размера файла msgbox.exe
Кликните здесь для просмотра всего текста
/ALIGN:размерзаголовоккодданныеимпортобщий размер msgbox.exe
не указан 1024 4644810242560 байт
256 768 20848512 1536 байт
12864080 483841152 байт
645768048256 960 байт
32 576 48 48192 864 байт
16 576 48 48176 848 байт
8????ошибка при создании файла
при /ALIGN:8 получаем сообщение об ошибке
msgbox.obj fatal error LNK1164: section 0x4 alignment (16) greater then /ALIGN value
похоже, что в этом направлении мы достигли предела, хотя от исходной программы в 2560 байт пришли к программе в 848 байт, а это согласитесь не плохо!
Шаг второй — объединяем сегмент кода и сегмент данных.
Наша программа использует два сегмента, сегмент кода и сегмент данных, посмотрите внимательно через hiew32 — между этими сегментами прослойка из нулей, от которых мы и пытаемся избавится. А помните во времена DOS'а можно было создавать COM-файлы, которые в единственном сегменте содержал и код, и стек, и данные? А нельзя ли и здесь создать, что-то подобное?
  1. Оказывается можно — при помощи ключа /MERGE:from=to можно присвоить секции кода (0С0000040h — атрибуты секции «выполнять» и «читать») функции секции данные (60000020h — атрибуты секции «читать» и «писать»)
    Вносим изменения в текст программы ― вместо
    Кликните здесь для просмотра всего текста
    Assembler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    .386
    .model flat
    include windows.inc
    include kernel32.inc
    include user32.inc
    includelib user32.lib
    includelib kernel32.lib
     
    .data
    MsgCaption      db "Iczelion's tutorial #2",0
    MsgBoxText      db "Win32 Assembly is Great!",0
     
    .code
    start:
        invoke MessageBox, NULL,addr MsgBoxText, addr MsgCaption, MB_OK
        invoke ExitProcess,NULL
    end start
    пишем
    Кликните здесь для просмотра всего текста
    Assembler
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    .386
    .model flat
    include windows.inc
    include kernel32.inc
    include user32.inc
    includelib user32.lib
    includelib kernel32.lib
     
    .code
    start:
        invoke MessageBox, NULL,addr MsgBoxText, addr MsgCaption, MB_OK
        invoke ExitProcess,NULL
     
    MsgCaption      db "Iczelion's tutorial #2",0
    MsgBoxText      db "Win32 Assembly is Great!",0
    end start
    в файл makeit.bat, создающий msgbox.exe
    Кликните здесь для просмотра всего текста
    Код
    \masm32\bin\ml /c /Cp /Gz /I\masm32\include /coff "msgbox.asm"
    \masm32\bin\Link /SUBSYSTEM:WINDOWS /ALIGN:16 /LIBPATH:\masm32\lib "msgbox.obj"
    вносим изменения
    Код
    \masm32\bin\ml /c /Cp /Gz /I\masm32\include /coff "msgbox.asm"
    \masm32\bin\Link /SUBSYSTEM:WINDOWS /ALIGN:16 /LIBPATH:\masm32\lib ^
    /MERGE:.rdata=.text "msgbox.obj"
    Обратите внимание на символ «^». Мне нравится, чтобы любой исходный текст, который я пишу, выглядел красиво — пусть это даже и bat-файл. Одним из обязательных условий красоты и удобочитаемости кода является его ширина: все строки должны умещаться на экране — это примерная ширина текста в 80 символов. При написании bat-файла основным требованием является, запись команды в одну строку. Символ «^» экранирует конец строки — это позволяет разместить содержимое длинной строки на экране в нескольких строках, а система, как и прежде, будет считать их одной длинной строкой.
  2. Объединяя сегмента кода и сегмента данных можно также добиться если использовать опцию линкера /SECTION. Опция командной строки компоновщика link.exe /SECTION:name,[[!]{DEKPRSW}][,ALIGN=#] позволяет принудительно назначать атрибуты секциям PE-файла. Для секции можно задать один или несколько атрибутов. Следует задавать все атрибуты, которые должна иметь секция; если какой-либо знак атрибута не указан, то его бит будет отключен. Если не указан атрибут R, W или E, то существующее состояние чтения, записи или исполнения остается неизменяемым. Чтобы инвертировать атрибут, перед его символом указывают знак «!». С помощью параметра ALIGN=# можно задать значение выравнивания для конкретной секции. Значения знаков атрибутов приведены в следующей таблице.
    Кликните здесь для просмотра всего текста
    буква аттрибут значение перевод
    D Discardable Marks the section as discardable Секция помечается как выгружаемая
    E Execute The section is executable Секция является выполняемой
    K Cacheable Marks the section as not cacheable Секция помечается как некэшируемая
    P Pageable Marks the section as not pageable Секция помечается как секция без страничной организации
    R Read Allows read operations on data Допускаются операции чтения данных
    S Shared Shares the section among all processes that load the image Секция совместно используется всеми процессами, загружающими образ
    W Write Allows write operations on data Допускаются операции записи данных
    В данном случае секции с именем «.text» (содержащей код программы) и уже имеющей атрибуты R (доступна для чтения) и E (исполнимая), устанавливается атрибут W (доступна для записи)
    Код
    /SECTION:.text,W
    соответственно измененный bat-файл должен выглядеть вот так:
    Кликните здесь для просмотра всего текста
    Код
    \masm32\bin\ml /c /Cp /Gz /I\masm32\include /coff "msgbox.asm"
    \masm32\bin\Link /SUBSYSTEM:WINDOWS /ALIGN:16 /LIBPATH:\masm32\lib ^
    /SECTION:.text,W "msgbox.obj"
В результате получаем msgbox.exe в 720 байт
Кликните здесь для просмотра всего текста
заголовок код и данные импорт общий размер
512 88 120 720 байт
Шаг третий — избавляемся от invoke-абельного вызова процедур
Заглянем в MsgBox.exe через hiew32
Кликните здесь для просмотра всего текста
Код
.00400230: 6A00          ; push MB_OK
.00400232: 6800304000    ; push offset MsgCaption
.00400237: 6819304000    ; push offset MsgBoxText
.0040023C: 6A00          ; push 0
.0040023E: E807000000    ; call [40024A];invoke MessageBox,NULL,addr MsgBoxText,addr MsgCaption,MB_OK
.00400243: 6A00          ; push 0
.00400245: E806000000    ; call [400250];invoke ExitProcess,NULL
.0040024A: FF250820400000; jmp MessageBoxA
.00400250: FF250020400000; jmp ExitProcess
.00400256:
  1. Откуда в нашей программе взялись команды jmp MessageBoxA и jmp ExitProcess? Ведь в исходном тексте нашей программы ее нет — это следствие того, что вместо push и call мы используем invoke-абельный вызов процедур. Если заменить invoke MessageBox на call _imp__MessageBoxA@16 и invoke ExitProcess на call _imp__ExitProcess@4, тогда мы сэкономим по 7 байт на каждом вызове API функции.
  2. Даже здесь на 38 байт кода (400256h - 400230h = 26h = 38) приходится 3 команды push 0 (код команды 6A00h) (3x2 = 6 байт) если предварительно обнулить регистр EBX (варианты: EBP, ESI или EDI (API-функции не изменяют значения в этих регистрах)), то push ebx (код 53h) это один байт и мы будем экономить по 1 байту, когда засылаем в стек значения 0. Команда xor ebx,ebx (код 33DBh) это 2 байта, учтем, что при старте приложения в Windows XP в eax находится ноль, а команда xchg eax,ebx (код 93h) это один байт.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.386
.model flat
include windows.inc
include user32.inc
includelib user32.lib
includelib kernel32.lib
extern _imp__MessageBoxA@16:dword
extern _imp__ExitProcess@4:dword
.code
start:  xchg ebx,eax
    push ebx;MB_OK
    push offset MsgCaption
    push offset MsgBoxText
    push ebx;NULL
    call _imp__MessageBoxA@16
    push ebx;NULL
    call _imp__ExitProcess@4
MsgCaption      db "Iczelion's tutorial #2",0
MsgBoxText      db "Win32 Assembly is Great!",0
end start
Кликните здесь для просмотра всего текста
Код
400200:  93              ;xchg ebx,eax;ebx:=0
400201:  53              ;push ebx;MB_OK
400202:  681A024000      ;push offset MsgCaption
400207:  6831024000      ;push offset MsgBoxText
40020C:  53              ;push ebx;0
40020D:  E807000000      ;call MessageBox
400213:  53              ;push ebx;0
400214:  E806000000      ;call ExitProcess
40021A:  49637A656C69...;'Iczelion''s tutorial #2',0
после всех изменений получаем msgbox.exe в 704 байта.
Кликните здесь для просмотра всего текста
заголовок код и данные импорт общий размер
512 76 116 704 байта
Шаг четвертый — алгоритмическая оптимизация
при старте нашего приложения в Ollydbg видно, что на вершине стека лежит адрес функции kernel32.TerminateProcess, а именно ее вызывает функция ExitProcess, поэтому возникает вопрос, а нужна ли нам в этой программе функция ExitProcess? Заменяем вызов ExitProcess на команду retn, которая извлечет из стека адрес функции TerminateProcess и передаст на нее управление.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.386
.model flat
include windows.inc
includelib user32.lib
extern _imp__MessageBoxA@16:dword
 
.code
 
start:  push eax    ;MB_OK
    push offset MsgCaption
    push offset MsgBoxText
    push eax    ;NULL
    call MessageBoxA@16
    retn
MsgCaption      db "Iczelion's tutorial #2",0
MsgBoxText      db "Win32 Assembly is Great!",0
end start
Кликните здесь для просмотра всего текста
Код
.004001F8:50           ;  push eax
.004001F9:680B024000   ;  push 40020Bh ;'Iczelion's tutorial #2'
.004001F9:6822024000   ;  push 400222h ;'Win32 Assembly is Great!'
.00400203:50           ;  push eax
.00400204:FF156002400  ;  call MessageBoxA
.0040020A:C3           ;  retn
.0040020B:49637A656C696F...;  "Iczelion's tutorial #2",0
Длина «чистого кода» 40020Bh - 4001F8h=13h=19 байт. Кроме уменьшения кода уменьшается также и секция импорта, ведь у нас не две функции из разных библиотек, а всего одна функция и одна подключаемая DLL.
Программа нормально запускается, ее длина 640 байт. Для уменьшения величины экзешника переходим к пятому шагу.
Кликните здесь для просмотра всего текста
Код
.00400240:   00 00 00 00 00 00 00 00  72 02 00 00 F0 01 00 00         r   Ё
.00400250:   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
.00400260:   00 00 00 00 00 00 4D 65  73 73 61 67 65 42 6F 78       MessageBox
.00400270:   41 00 75 73 65 72 33 32  2E 64 6C 6C 00 00 00 00 A user32.dll

Шаг пятый — «хирургический» — ампутируем у файла «хвост»
hiew32 показывает, что в хвосте нашего файла, сразу за строкой «user32.dll» целых четыре байта содержащих нули. А не удалить ли их нам вручную? Сказано — сделано! В far'е нажимаем F4 открываем файл на редактирование, нажимаем Ctrl+End и переходим в конец файла. Далее, нажимая на Backspace, удаляем лишнее, жмем на F2 и сохраняем изменения в файле, нажимаем на F10 и выходим из файла — размер msgbox.exe стал 632 байт. Нажимаем на него и запускаем msgbox.exe — запускается нормально! Стоп-стоп! 640-632=8 байт, а должно быть 4! Смотрим в hiew32 — о, боже!, мы отрезали вместе с нулями от «user32.dll» кусок «.dll», но файл-то всё равно работает! Запомним на будущее, что от DLL достаточно только названия, а точку и расширение файла система подставит сама.
Кликните здесь для просмотра всего текста
заголовок код и данные импорт общий размер
504 68 60 632 байта
Шаг шестой — уменьшаем DOS-stub.
Код нашей программы начинается с 1F8h=504 байта. Всё, что выше — это заголовок нашего EXE-файла — вот бы его уменьшить! Заголовок нашего файла состоит из двух частей. От строки «MZ» до строки «PE» заголовок DOS (0C8h=200 байт) и от строки «PE» до 1F8h=504 байта PE-заголовок (304 байта). Адрес строки «PE» содержится в DOS-заголовке в двойном слове по смещению 3Ch.
Начнем с уменьшения DOS-стаба. Возьмем hiew32.exe и создадим с его помощью вот такой файл:
Кликните здесь для просмотра всего текста
Код
.000000:   4D 5A 00 00 00 00 00 00  00 00 00 00 00 00 00 00 MZ
.000010:   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
.000020:   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
.000030:   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
Присвоим ему имя stub.exe и используем в качестве stub-программы в очередной раз, внеся изменения в makeit.bat.
Кликните здесь для просмотра всего текста
Код
\masm32\bin\ml /c /Cp /Gz /I\masm32\include /coff "msgbox.asm"
\masm32\bin\Link /SUBSYSTEM:WINDOWS /ALIGN:16 /LIBPATH:\masm32\lib ^
/MERGE:.rdata=.text /STUB:stubby.exe "msgbox.obj"
Запускаем makeit.bat и получаем msgbox.exe в 576 байт, если еще отрезать конечные нулевые байты и «.dll», тогда его размер равен 568 байт. Хотя через hiew32 видно, что размер DOS-заголовка не 64 байта, как мы предполагали, а 112 байт, но это не 200 байт, как было в начале.
Кликните здесь для просмотра всего текста
заголовок код и данные импорт общий размер
440 68 60 568 байта
Шаг седьмой — создание заголовка PE-файла вручную
Нам потребуется создать bin-файл, заголовок которого мы напишем самостоятельно. Для этого требуются ассемблеры, которые могут создавать бинарные файлы (NASM, FASM), но у них немного другой синтаксис. Мы же при помощи MASM создадим обычный COM-файл. Для создания COM-файла нам потребуется ml.exe версии 6.14 и какой-нибудь старенький link.exe, который бы понимал ключ /t (например 5.60 версии), bat-файл нам тоже придется переделать
Кликните здесь для просмотра всего текста
Код
ml /AT /c /Cp msgbox.asm
Link16 /t msgbox.obj ,msgbox.exe;
del msgbox.obj
нам потребуется немного знаний о формате исполняемых файлов PE и процессе их загрузки в память — всё это Вы узнаете в следующей главе, а пока примите как есть.

На сайте WASM.RU в разделе СТАТЬИ http://www.cyberforum.ru/cgi-bin/latex.cgi?\rightarrow Assembler.Ru
есть статья «Минимальная stub-программа», где Svet®off отвечает на вопрос: «Можно ли создать stub-программу короче 40h байт?» Так как у программы со стабом короче 64 байт смещение от начала файла 3Ch (поле e_lfanew) попадает уже внутрь PE-заголовка, то нужно, чтобы он не попал на поле PE-заголовка имеющее критическое значение при загрузке файла.
  • Сперва в статье приводится пример stub'а длиной в 20h=32 байта, смещение 3Ch от начала файла приходится на смещение 3Ch-20h=1Ch PE-заголовка. Здесь расположен dword, содержащий размер секций кода (Size of code).
    По одним источникам — это поле используется для первичного отведения памяти под приложение. По другим — не используются вообще. Если поместить туда число 20h, то практическая проверка показывает, что приложение с таким stub'ом работает нормально.
  • Далее приводится пример рабочего stub'а длиной в 18h=24 байта, смещение 3Ch от начала файла приходится на смещение 3Сh-18h=24h PE-заголовка. Здесь расположен dword, содержащий размер неинициализированной секции данных (Size of uninitialized data). При размещении там числа 18h получаем работоспособное приложение.
  • При уменьшении длины stub до 0Ch=12 байт, смещение 3Ch от начала файла приходится на смещение 3Ch-0Ch=30h PE-заголовка. Здесь расположен dword, содержащий относительное смещение сегмента неинициализированных данных («.bss» сегмент) в загруженном файле (Base of data).
    При размещении там числа 0Сh получаем работоспособное приложение.
  • А можно расположить сигнатуру «PE» сразу после сигнатуры «MZ»? Нет — потому что сигнатура «PE» должна быть выровнена по границе двойного слова. Но если мы сдвинемся на 2 байта, тогда условие выравнивания будет соблюдено и мы получим наименьший размер, которого можно добиться от old-exe-заголовка. Правда работать такой файл будет только под Windows XP. Смещение 3Ch от начала файла приходится на смещение 3Сh-4=38h PE-заголовка. Здесь расположен dword, содержащий размер кратности выравнивания секций в памяти (SectionAlignment).
    Значение Section Alignment должно быть больше или равно 1000h байт, но если File Alignment = Section Alignment, то последнее может принимать любое значение, представляющее собой степень двойки. При Section Alignment = File Alignment = 4 получаем работоспособное приложение.
Кликните здесь для просмотра всего текста
смещение от "PE" размерность название комментарий
   Файловый заголовок
+04h WORD Machine Тип центрального процессора
+06h WORD NumberOfSections Количество секций
+08h DWORD TimeDateStamp Информация о времени, когда был собран данный PE-файл
+0Ch DWORD PointerToSymbolTable Указатель на размер отладочной информации
+10h DWORD NumberOfSymbols Указатель на COFF-таблицу символов PE-формата
+14h WORD SizeOfOptionalHeader Размер опционального заголовка
+16h WORD Characteristics Атрибуты файла
   Стандартные поля NT
+18h WORD Magic Состояние отображаемого файла
+1Ah BYTE MajorLinkerVersion Содержат версию линковщика, создавшего данный файл.
+1Bh BYTE MinorLinkerVersion  
+1Ch DWORD SizeOfCode Суммарный размер секций кода
+20h DWORD SizeOfInitializedData Суммарный размер инициализированных данных
+24h DWORD SizeOfUninitializedData Суммарный размер неинициализированных данных
+28h DWORD AddressOfEntryPoint Относительный адрес точки входа, отсчитываемый от начала Image Base. Из всех стандартных полей, поле AddressOfEntryPoint является наиболее интересным для формата PE файлов. Это поле содержит адрес точки входа приложения, и, что, вероятно, более важно для для хакеров, местоположение конца Import Address Table (таблицы импортируемых адресов ― IAT).
+2Ch DWORD BaseOfCode Относительные базовые адреса кодовой секции. Относительное смещение сегмента кода («.text» сегмент) в загруженном файле.
+30h DWORD BaseOfData Относительные базовые адреса секции данных. Относительное смещение сегмента неинициализированных данных («.bss» сегмент) в загруженном файле.
   Дополнительные поля NT
+34h DWORD ImageBase Базовый адрес загрузки страничного имиджа. Предпочтительный адрес в адресном пространстве процесса для загрузки исполнимого файла. Линковщик подставляет «значение по умолчанию» равное 400000h, но это значение можно изменить с помощью опции линковщика BASE:.
+38h DWORD SectionAlignment Кратность выравнивания секций в памяти. Сегменты загружаются в адресное пространство процесса последовательно, начиная с ImageBase. SectionAlignment предписывает минимальный размер, который сегмент может занять при загрузке ― так что сегменты оказываются выровненными по границе SectionAlignment. Выравнивание сегмента не может быть меньше размера страницы (в настоящий момент 4096 байт на платформе x86), и должно быть кратно размеру страницы, как предписывает поведение менеджера виртуальной памяти Windows NT. 4096 (=1000h) байт являются «значением по умолчанию», но может быть установлено любое другое значение, при использовании опции линковщика ALIGN:
Еще один путь к уменьшению PE-заголовка это уменьшение таблицы разделов, по умолчанию количество разделов должно быть равным 16, нам же требуется только секция импорта. В таблице разделов секция импорта идет сразу после секции экспорта. Хотя мы указали в таблице разделов 2 секции, но загрузчик проверит содержимое секции debug и, если debug_size не равен нулю, тогда при загрузке файла система выдаст сообщение

У нас поле debug_size (смещение +0ACh от сигнатуры "PE") попадает на флаги секции код, там находится число 0E00000020h
20h(секция кода)+20000000h(разрешено исполнение)+40000000h(разрешено чтение)+80000000h(разрешена запись)=0E00000020h
А что если обнулить флаги секции код? Ставим по адресу debug_size 0 и запускаем приложение — О, чудо! Этого не должно было быть, но приложение запускается!
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
.586p
.model tiny
include windows.inc
.code
exebase         equ 400000h
main:
;signatures----------------------------
dosHeader       dd IMAGE_DOS_SIGNATURE;сигнатура 'MZ'
ntHeader        dd IMAGE_NT_SIGNATURE;сигнатура 'PE'
;image_header--------------------------
Machine         dw IMAGE_FILE_MACHINE_I386;Процессор Intel386
Count_of_section    dw 1;число секций = 1
TimeStump       dd 0
Symbol_table_offset dd 0
Symbol_table_count  dd 0
Size_of_optional_header dw section_table-optional_header;Размер NT-заголовка обычно E0h, у нас 70h
Characteristics     dw IMAGE_FILE_32BIT_MACHINE or \
 IMAGE_FILE_RELOCS_STRIPPED or IMAGE_FILE_EXECUTABLE_IMAGE or \
 IMAGE_FILE_LINE_NUMS_STRIPPED or IMAGE_FILE_LOCAL_SYMS_STRIPPED;флаги программы 100h+1+2+4+8=10Fh
;-------------------------------------
optional_header:
Magic_optional_header   dw IMAGE_NT_OPTIONAL_HDR32_MAGIC;"магическое" значение 10Bh
Linker_version_major_and_minor dw 0 
Size_of_code        dd end_import-start
Size_of_init_data   dd 0
Size_of_uninit_data dd 0
entry_point     dd start
base_of_code        dd start
base_of_data        dd 0
image_base      dd exebase;=400000h
e_lfanew        dd ntHeader-dosHeader;=section alignment=4
file_alignment      dd 4
OS_version_major_minor  dd 4
image_version_major_minor dd 0
subsystem_version_major_minor dd 4
reserved        dd 0
size_of_image       dd end_import
size_of_header      dd start
checksum        dd 0
subsystem_and_DLL_flag  dd IMAGE_SUBSYSTEM_WINDOWS_GUI;=2
Stack_allocation    dd 100000h
Stack_commit        dd 1000h
Heap_allocation     dd 100000h
Heap_commit     dd 1000h
loader_flag     dd 0
number_of_dirs      dd (section_table-export_RVA)/8;=2
export_RVA          dd 0
export_size         dd 0
import_RVA          dd import
import_size         dd end_import-import
;------------------------------------------------
section_table       dd 'xet.','t';resource_RVA_and_resource_size
virtual_size        dd 0;exeption_RVA 
virtual_address     dd start;exeption_size 
Physical_size       dd end_import-start;security_RVA 
Physical_offset     dd start;security_size 
Relocations         dd 0;fixups_RVA        
Linenumbers     dd 0;fixups_size       
Relocations_and_Linenumbers_count dd 0;debug_RVA 
Attributes              dd 0;debug_size          
;---------------------------------------------------------------------
start:  push eax;MB_OK              
    push offset wTitle+exebase  
    push offset Message+exebase 
    push eax                    
    call MessageBox+exebase     
    retn                        
;----------------------------------------------------------------------
wTitle  db 'Iczelion Tutorial #2',0;заголовок MessageBox'а
Message db 'Win32 Assembly is Great!';выводимое сообщение
;----------------------------------------------------------------------
import  dd 0,0,0;значение смещения от начала секции import до начала массива 
;с адресами импортируемых функций ArrayAddressImpFunc (первая копия)
    dd user32_dll;значение смещения от начала секции import
;до начала ACSIIZ-строки с именем dll-библиотеки
    dd MessageBox,0,0;значение смещения от начала секции import до начала
;массива с адресами импортируемых функций ArrayAddressImpFunc (вторая копия)
MessageBox      dd _MessageBoxA
        dw 0          
_MessageBoxA db 0,0,'MessageBoxA',0 ;          
user32_dll  db 'user32'                      
end_import:
end main

размер msgbox.exe равен 298 байтам или 11,6% от начального размера в 2560 байт.
Кликните здесь для просмотра всего текста
заголовок код и данные импорт общий размер
180 64 54 298 байт
А теперь, чтобы эстеты не упрекали нас в том, что «для создания такого маленького EXE-файла мы использовали так много текста» — сохраним наш заголовок в файле capito.asm
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
;signatures----------------------------
dosHeader       dd IMAGE_DOS_SIGNATURE;сигнатура 'MZ'
ntHeader        dd IMAGE_NT_SIGNATURE;сигнатура 'PE'
;image_header--------------------------
Machine         dw IMAGE_FILE_MACHINE_I386;Процессор Intel386
Count_of_section    dw 1;число секций = 1
TimeStump       dd 0
Symbol_table_offset dd 0
Symbol_table_count  dd 0
Size_of_optional_header dw section_table-optional_header;Размер NT-заголовка обычно E0h, у нас 70h
Characteristics     dw IMAGE_FILE_32BIT_MACHINE or \
 IMAGE_FILE_RELOCS_STRIPPED or IMAGE_FILE_EXECUTABLE_IMAGE or \
 IMAGE_FILE_LINE_NUMS_STRIPPED or IMAGE_FILE_LOCAL_SYMS_STRIPPED;флаги программы 100h+1+2+4+8=10Fh
;-------------------------------------
optional_header:
Magic_optional_header   dw IMAGE_NT_OPTIONAL_HDR32_MAGIC;"магическое" значение 10Bh
Linker_version_major_and_minor dw 0 
Size_of_code        dd end_import-start
Size_of_init_data   dd 0
Size_of_uninit_data dd 0
entry_point     dd start
base_of_code        dd start
base_of_data        dd 0
image_base      dd exebase;400000h
e_lfanew        dd ntHeader-dosHeader;section alignment 4
file_alignment      dd 4
OS_version_major_minor  dd 4
image_version_major_minor dd 0
subsystem_version_major_minor dd 4
reserved        dd 0
size_of_image       dd end_import
size_of_header      dd start
checksum        dd 0
subsystem_and_DLL_flag  dd IMAGE_SUBSYSTEM_WINDOWS_GUI;2
Stack_allocation    dd 100000h
Stack_commit        dd 1000h
Heap_allocation     dd 100000h
Heap_commit     dd 1000h
loader_flag     dd 0
number_of_dirs      dd (section_table-export_RVA)/8
export_RVA          dd 0
export_size         dd 0
import_RVA          dd import
import_size         dd end_import-import
;------------------------------------------------
section_table       dd 'xet.','t'
virtual_size        dd 0
virtual_address     dd start
Physical_size       dd end_import-start
Physical_offset     dd start
Relocations         dd 0
Linenumbers     dd 0
Relocations_and_Linenumbers_count dd 0
Attributes              dd 0
Теперь наш ассемблерный текст выглядит так:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.586p
.model tiny
include windows.inc
.code
exebase         equ 400000h
main:
include capito.asm
;---------------------------------------------------------------------
start:  push eax;MB_OK              
    push offset wTitle+exebase  
    push offset Message+exebase 
    push eax                    
    call MessageBox+exebase     
    retn                        
;----------------------------------------------------------------------
wTitle  db 'Iczelion Tutorial #2',0;заголовок MessageBox'а
Message db 'Win32 Assembly is Great!';выводимое сообщение
;----------------------------------------------------------------------
import  dd 0,0,0,user32_dll
    dd MessageBox,0,0            
MessageBox      dd _MessageBoxA
        dw 0          
_MessageBoxA db 0,0,'MessageBoxA',0
user32_dll  db 'user32'                      
end_import:
end main
Размер EXE-файла по прежнему 298 байт, а файл capito.asm мы будем использовать во всех наших уроках Iczelion'a
Чтобы «добить» эстетов — создаем «на коленке» собственный макрос INVOKE и помещаем его в capito.asm
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
OPTION NOKEYWORD: <invoke>; OPTION NOKEYWORD позволяет переопределить зарезервироаннное в масме слово INVOKE
invoke MACRO Fn,args:VARARG;после имени макроса invoke идут формальные параметры Fn и args. 
;Причем формальный параметр args представляет из себя строку формальных параметров разделенных запятыми
LOCAL txt,arg;в нашем макросе используются локальные переменные txt и arg 
    txt TEXTEQU <>;инициируем переменную txt как пустую строку
    IRP arg,<args>;IRP-блок, заканчивается на ближайшем ENDM. Формальный параметр arg будет заменятся 
;на фактические параметры из строки args. Параметры из строки args разделены запятыми
    txt CATSTR <arg>, <!,>, txt;в IRP-блоке параметры из args "подклеиваются" к строке txt слева, 
;между параметрами arg ставятся запятые, <!,> -- макрооператор "!" не переносится в окончательный текст, 
;но следующая за ним запятая трактуется не как разделитель параметров, а как обычный ASCII-символ
    ENDM;конец IRP-блока
%   IRP arg,<txt>;встретив конструкцию % <константное значение> в фактическом параметре, 
;макрогенератор вычисляет указанное выражение и подставляет его значение вместо всей этой конструкции
    push arg;параметры arg "пушатся" в правильном порядке
    ENDM;конец IRP-блока
    call Fn+exebase; вызывается процедура, имя которой было передано, как фактический параметр
ENDM;конец макроса
И теперь наши asm-файл практически не отличается от исходного, а ехе-файл «весит» очень мало
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
; masm dos com #
.586p
.model tiny
;for WinXP - 298 bytes
include windows.inc
.code
exebase equ 400000h
main:
include capito.asm
;----------------------------------
start:  invoke MessageBox,eax,offset Message+exebase,offset wTitle+exebase,eax
    retn                        
;----------------------------------
wTitle  db 'Iczelion Tutorial #2',0   
Message db 'Win32 Assembly is Great!'  
;-----------------------------------
import  dd 0,0,0,user32_dll,MessageBox,0,0           
MessageBox      dd _MessageBoxA
        dw 0          
_MessageBoxA    db 0,0,'MessageBoxA',0
user32_dll  db 'user32'                      
end_import:
end main

__________________________________
© Mikl___ 2013
7
Mikl___
Заблокирован
Автор FAQ
03.01.2013, 07:39  [ТС] #5
Еще один путь к уменьшению PE-заголовка это уменьшение таблицы разделов, по умолчанию количество разделов должно быть равным 16, нам же требуется только секция импорта. В таблице разделов секция импорта идет сразу после секции экспорта. Хотя мы указали в таблице разделов 2 секции, но загрузчик проверит содержимое секции debug и, если debug_size не равен нулю, тогда при загрузке файла система выдаст сообщение

У нас поле debug_size (смещение +0ACh от сигнатуры "PE") попадает на флаги секции код, там находится число 0E00000020h
20h(секция кода)+20000000h(разрешено исполнение)+40000000h(разрешено чтение)+80000000h(разрешена запись)=0E00000020h
А что если обнулить флаги секции код? Ставим по адресу debug_size 0 и запускаем приложение — О, чудо! Этого не должно было быть, но приложение запускается!
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
.586p
.model tiny
include windows.inc
.code
exebase         equ 400000h
main:
;signatures----------------------------
dosHeader       dd IMAGE_DOS_SIGNATURE;сигнатура 'MZ'
ntHeader        dd IMAGE_NT_SIGNATURE;сигнатура 'PE'
;image_header--------------------------
Machine         dw IMAGE_FILE_MACHINE_I386;Процессор Intel386
Count_of_section    dw 1;число секций = 1
TimeStump       dd 0
Symbol_table_offset dd 0
Symbol_table_count  dd 0
Size_of_optional_header dw section_table-optional_header;Размер NT-заголовка обычно E0h, у нас 70h
Characteristics     dw IMAGE_FILE_32BIT_MACHINE or \
 IMAGE_FILE_RELOCS_STRIPPED or IMAGE_FILE_EXECUTABLE_IMAGE or \
 IMAGE_FILE_LINE_NUMS_STRIPPED or IMAGE_FILE_LOCAL_SYMS_STRIPPED;флаги программы 100h+1+2+4+8=10Fh
;-------------------------------------
optional_header:
Magic_optional_header   dw IMAGE_NT_OPTIONAL_HDR32_MAGIC;"магическое" значение 10Bh
Linker_version_major_and_minor dw 0 
Size_of_code        dd end_import-start
Size_of_init_data   dd 0
Size_of_uninit_data dd 0
entry_point     dd start
base_of_code        dd start
base_of_data        dd 0
image_base      dd exebase;=400000h
e_lfanew        dd ntHeader-dosHeader;=section alignment=4
file_alignment      dd 4
OS_version_major_minor  dd 4
image_version_major_minor dd 0
subsystem_version_major_minor dd 4
reserved        dd 0
size_of_image       dd end_import
size_of_header      dd start
checksum        dd 0
subsystem_and_DLL_flag  dd IMAGE_SUBSYSTEM_WINDOWS_GUI;=2
Stack_allocation    dd 100000h
Stack_commit        dd 1000h
Heap_allocation     dd 100000h
Heap_commit     dd 1000h
loader_flag     dd 0
number_of_dirs      dd (section_table-export_RVA)/8;=2
export_RVA          dd 0
export_size         dd 0
import_RVA          dd import
import_size         dd end_import-import
;------------------------------------------------
section_table       dd 'xet.','t';resource_RVA_and_resource_size
virtual_size        dd 0;exeption_RVA 
virtual_address     dd start;exeption_size 
Physical_size       dd end_import-start;security_RVA 
Physical_offset     dd start;security_size 
Relocations         dd 0;fixups_RVA        
Linenumbers     dd 0;fixups_size       
Relocations_and_Linenumbers_count dd 0;debug_RVA 
Attributes              dd 0;debug_size          
;---------------------------------------------------------------------
start:  push eax;MB_OK              
    push offset wTitle+exebase  
    push offset Message+exebase 
    push eax                    
    call MessageBox+exebase     
    retn                        
;----------------------------------------------------------------------
wTitle  db 'Iczelion Tutorial #2',0;заголовок MessageBox'а
Message db 'Win32 Assembly is Great!';выводимое сообщение
;----------------------------------------------------------------------
import  dd 0,0,0;значение смещения от начала секции import до начала массива 
;с адресами импортируемых функций ArrayAddressImpFunc (первая копия)
    dd user32_dll;значение смещения от начала секции import
;до начала ACSIIZ-строки с именем dll-библиотеки
    dd MessageBox,0,0;значение смещения от начала секции import до начала
;массива с адресами импортируемых функций ArrayAddressImpFunc (вторая копия)
MessageBox      dd _MessageBoxA
        dw 0          
_MessageBoxA db 0,0,'MessageBoxA',0 ;          
user32_dll  db 'user32'                      
end_import:
end main

размер msgbox.exe равен 298 байтам или 11,6% от начального размера в 2560 байт.
Кликните здесь для просмотра всего текста
заголовок код и данные импорт общий размер
180 64 54 298 байт
А теперь, чтобы эстеты не упрекали нас в том, что «для создания такого маленького EXE-файла мы использовали так много текста» — сохраним наш заголовок в файле capito.asm
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
;signatures----------------------------
dosHeader       dd IMAGE_DOS_SIGNATURE;сигнатура 'MZ'
ntHeader        dd IMAGE_NT_SIGNATURE;сигнатура 'PE'
;image_header--------------------------
Machine         dw IMAGE_FILE_MACHINE_I386;Процессор Intel386
Count_of_section    dw 1;число секций = 1
TimeStump       dd 0
Symbol_table_offset dd 0
Symbol_table_count  dd 0
Size_of_optional_header dw section_table-optional_header;Размер NT-заголовка обычно E0h, у нас 70h
Characteristics     dw IMAGE_FILE_32BIT_MACHINE or \
 IMAGE_FILE_RELOCS_STRIPPED or IMAGE_FILE_EXECUTABLE_IMAGE or \
 IMAGE_FILE_LINE_NUMS_STRIPPED or IMAGE_FILE_LOCAL_SYMS_STRIPPED;флаги программы 100h+1+2+4+8=10Fh
;-------------------------------------
optional_header:
Magic_optional_header   dw IMAGE_NT_OPTIONAL_HDR32_MAGIC;"магическое" значение 10Bh
Linker_version_major_and_minor dw 0 
Size_of_code        dd end_import-start
Size_of_init_data   dd 0
Size_of_uninit_data dd 0
entry_point     dd start
base_of_code        dd start
base_of_data        dd 0
image_base      dd exebase;400000h
e_lfanew        dd ntHeader-dosHeader;section alignment 4
file_alignment      dd 4
OS_version_major_minor  dd 4
image_version_major_minor dd 0
subsystem_version_major_minor dd 4
reserved        dd 0
size_of_image       dd end_import
size_of_header      dd start
checksum        dd 0
subsystem_and_DLL_flag  dd IMAGE_SUBSYSTEM_WINDOWS_GUI;2
Stack_allocation    dd 100000h
Stack_commit        dd 1000h
Heap_allocation     dd 100000h
Heap_commit     dd 1000h
loader_flag     dd 0
number_of_dirs      dd (section_table-export_RVA)/8
export_RVA          dd 0
export_size         dd 0
import_RVA          dd import
import_size         dd end_import-import
;------------------------------------------------
section_table       dd 'xet.','t'
virtual_size        dd 0
virtual_address     dd start
Physical_size       dd end_import-start
Physical_offset     dd start
Relocations         dd 0
Linenumbers     dd 0
Relocations_and_Linenumbers_count dd 0
Attributes              dd 0
Теперь наш ассемблерный текст выглядит так:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.586p
.model tiny
include windows.inc
.code
exebase         equ 400000h
main:
include capito.asm
;---------------------------------------------------------------------
start:  push eax;MB_OK              
    push offset wTitle+exebase  
    push offset Message+exebase 
    push eax                    
    call MessageBox+exebase     
    retn                        
;----------------------------------------------------------------------
wTitle  db 'Iczelion Tutorial #2',0;заголовок MessageBox'а
Message db 'Win32 Assembly is Great!';выводимое сообщение
;----------------------------------------------------------------------
import  dd 0,0,0,user32_dll
    dd MessageBox,0,0            
MessageBox      dd _MessageBoxA
        dw 0          
_MessageBoxA db 0,0,'MessageBoxA',0
user32_dll  db 'user32'                      
end_import:
end main
Размер EXE-файла по прежнему 298 байт, а файл capito.asm мы будем использовать во всех наших уроках Iczelion'a
Чтобы «добить» эстетов — создаем «на коленке» собственный макрос INVOKE и помещаем его в capito.asm
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
OPTION NOKEYWORD: <invoke>; OPTION NOKEYWORD позволяет переопределить зарезервироаннное в масме слово INVOKE
invoke MACRO Fn,args:VARARG;после имени макроса invoke идут формальные параметры Fn и args. 
;Причем формальный параметр args представляет из себя строку формальных параметров разделенных запятыми
LOCAL txt,arg;в нашем макросе используются локальные переменные txt и arg 
    txt TEXTEQU <>;инициируем переменную txt как пустую строку
    IRP arg,<args>;IRP-блок, заканчивается на ближайшем ENDM. Формальный параметр arg будет заменятся 
;на фактические параметры из строки args. Параметры из строки args разделены запятыми
    txt CATSTR <arg>, <!,>, txt;в IRP-блоке параметры из args "подклеиваются" к строке txt слева, 
;между параметрами arg ставятся запятые, <!,> -- макрооператор "!" не переносится в окончательный текст, 
;но следующая за ним запятая трактуется не как разделитель параметров, а как обычный ASCII-символ
    ENDM;конец IRP-блока
%   IRP arg,<txt>;встретив конструкцию % <константное значение> в фактическом параметре, 
;макрогенератор вычисляет указанное выражение и подставляет его значение вместо всей этой конструкции
    push arg;параметры arg "пушатся" в правильном порядке
    ENDM;конец IRP-блока
    call Fn+exebase; вызывается процедура, имя которой было передано, как фактический параметр
ENDM;конец макроса
И теперь наши asm-файл практически не отличается от исходного, а ехе-файл «весит» очень мало
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
; masm dos com #
.586p
.model tiny
;for WinXP - 298 bytes
include windows.inc
.code
exebase equ 400000h
main:
include capito.asm
;----------------------------------
start:  invoke MessageBox,eax,offset Message+exebase,offset wTitle+exebase,eax
    retn                        
;----------------------------------
wTitle  db 'Iczelion Tutorial #2',0   
Message db 'Win32 Assembly is Great!'  
;-----------------------------------
import  dd 0,0,0,user32_dll,MessageBox,0,0           
MessageBox      dd _MessageBoxA
        dw 0          
_MessageBoxA    db 0,0,'MessageBoxA',0
user32_dll  db 'user32'                      
end_import:
end main



© Mikl___ 2013
Win32 API. Урок 2c. Еще больше уменьшаем MessageBox

Постараемся еще больше сжать нашу программу. Далее приведен текст программы, которая, получив у системного загрузчика адреса WinAPI-функций LoadLibraryA и MessageBox, создает файл tiny97.exe, состоящий только из минимального заголовка PE-файла. Внутри этого заголовка, в тех его полях, которыми можно пренебречь, расположен код программы, выводящей MessageBox, с заголовком и текстом "user32".
Кликните здесь для просмотра всего текста
Приступим к конструированию нашего PE-файла. Примем минимально возможные размеры выравнивания секций ― 4 байта ― и файла ― 4 байт.
Образ нашей программы будет состоять всего из одной секции, в которой будут размещены и данные, и код
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
.686P
.model flat
include windows.inc
includelib user32.lib
includelib kernel32.lib
extern _imp__MessageBoxA@16:dword
extern _imp__WriteFile@20:dword
extern _imp__CreateFileA@28:dword
extern _imp__CloseHandle@4:dword
extern _imp__LoadLibraryA@4:dword
.code
start:  xchg ebx,eax
        push MB_ICONINFORMATION OR MB_SYSTEMMODAL;1040h
        push offset szInfoCap 
        push offset namefile
        push ebx
    call _imp__MessageBoxA@16
    mov eax,_imp__LoadLibraryA@4
    sub eax,offset _LoadLibraryA-buffer+ImageBase+size _LoadLibraryA;400023h
    mov _LoadLibraryA,eax
    mov eax,_imp__MessageBoxA@16
    sub eax,offset _MessageBoxA-buffer+ImageBase+size _MessageBoxA;400035h
    mov _MessageBoxA,eax
    push ebx    ;NULL   
    push FILE_ATTRIBUTE_ARCHIVE
    push CREATE_ALWAYS
    push ebx
    push FILE_SHARE_READ or FILE_SHARE_WRITE
    push GENERIC_READ or GENERIC_WRITE
    push offset namefile
    call _imp__CreateFileA@28
    push eax    ;hFile для CloseHandle
    push ebx        ;lpOverlapped
        push offset SizeReadWrite   ;lpNumberOfBytesToWrite
    push sizeof_image;a4-buffer ;nNumberOfBytesToWrite=97
    push offset buffer  ;lpBuffer
    push eax    ;hFile для WriteFile
    call _imp__WriteFile@20
    call _imp__CloseHandle@4
QUIT:   retn
ImageBase equ 400000h
buffer  dd 'ZM','EP'
    dw 14Ch ;Machine (Intel 386)
    dw 0    ;NumberOfSection
EntryPoint: xor ebx,ebx ; ebx = 0
    mov edi,offset namedll-buffer+ImageBase
    push edi        ;push offset user32
    jmp short @f
    db 0,0  ;       UNUSED
    dw a4-optheader ;SizeOfOptionalHeader
    dw 103h ;Characteristics (no relocations, executable, 32 bit)
optheader:
    dw 10Bh ;Magic PE32
@@:
    db 0E8h         ;call LoadLibraryA
_LoadLibraryA dd 0
    push ebx        ;push 0
    push edi        ;push offset user32
    push edi        ;push offset user32
    push ebx        ;push 0
    jmp short @f
    db 0,0,0
    dd EntryPoint-buffer
@@:
    db 0E8h         ;call MessageBoxA
_MessageBoxA dd 0
    retn
    dw 0    ;           UNUSED
    dd ImageBase    ;ImageBase
    dd 4    ;SectionAligment
    dd 4    ;FileAligment
namedll db 'user32',0,0 ;       UNUSED
    dd 4    ;MinorSubsystemVersion  UNUSED
    dd 0    ;Win32VersionValue  UNUSED
    dd 68h  ;SizeOfimage
    dd sizeof_image;64h ;SizeOfHeader
    dd 0    ;CheckSum       UNUSED
    db 2    ;Subsystem (Win32 GUI)
a4:
;---------------------------------------------------------------------------
sizeof_image=$-buffer
szInfoCap db "Creator tiny MessageBox",0
namefile db 'tiny97.exe',0
SizeReadWrite dd 0
end start

размер tiny97.exe равен 97 байтов или 3,8% от начального размера в 2560 байт.
  1. Получение непосредственных адресов импортируемых функций LoadLibraryA и MessageBox от «материнской» программы позволяет отказаться от использования таблицы импорта в нашей программе и заменить косвенный 6-байтовый вызов функций с кодом 0FF.15.XX.XX.XX.XXh на 5-байтовый код 0EB.XX.XX.XX.XXh (непосредственный CALL)
  2. 97 байт являются теоретическим минимумом для длины PE-файла. По смещению +5Ch от сигнатуры «PE» находится поле Subsystem. Размещение в этом поле нулевого значения или отсутствие этого поля в файле воспринимается системным загрузчиком, как значение IMAGE_SUBSYSTEM_UNKNOWN (=0) и система не станет загружать такой файл. Поэтому по смещению +5Ch от «PE» должно находится ненулевое значение. Так как мы поместим в это поле значение IMAGE_SUBSYSTEM_WINDOWS_GUI=2, то под поле Subsystem достаточно отвести 1 байт. Итого: 4 байта (MZ-заголовок) + 5Ch (размер PE-заголовка до поля «Subsystem») + 1 байт (под собственно поле «Subsystem») = 61h = 97 байт
________________________________________________________
Для написания данной главы использовались работы следующих авторов:
  1. Solar Eclipse. Creating the smallest possible PE executable
  2. Крис Касперски. Сверхбыстрый импорт API-функций.
  3. Ivan2k. Выключалка компа на 97 байт подробности (на англ.) смотреть тут и тут
_______________________
© Mikl___ 2013
12
Изображения
 
Mikl___
Заблокирован
Автор FAQ
03.01.2013, 16:04  [ТС] #6
Win32 API. Урок 2b. MessageBox
Oбщие концепции и требования, предъявляемые к PE-файлам
Формат PE ― «переносимый исполняемый формат файлов» (portable executable file format). «Переносимым» он называется потому, что это единственный для всех 32/64-разрядных операционных систем Windows (9x, NT) формат исполнимых файлов. PE-формат впервые был использован в ОС Windows 3.1. В 1993 году формат был стандартизирован, он базируется на COFF-формате (Common Object File Format), который используется в нескольких UNIX и VMS. Начиная с Windows 3.XX и OS/2, EXE-файлам были присвоены сигнатуры файлов, чтобы указать целевую платформу исполнения. Для PE-файлов, сигнатура расположена непосредственно перед структурой заголовка PE-файла. В Windows 3.XX и OS/2 размер сигнатуры ― слово. Для PE-файлов размер сигнатуры ― двойное слово.
Кликните здесь для просмотра всего текста
название сигнатуры в файле WINNT.H размер числовое значение ASCIIзначение комментарий
IMAGE_DOS_SIGNATURE WORD 5A4Dh «MZ» инициалы Марка Джозефа Збиковского (Mark Zbikowski) одного из создателей операционной системы MS-DOS. Обычный формат исполняемых файлов в 16-разрядной операционной системе DOS.
WORD 4D5Ah «ZM» Windows NT и 9x поддерживают недокументированную сигнатуру «ZM», передающую управление на DOS-заглушку.
IMAGE_OS2_SIGNATURE WORD 454Eh «NE» New Executable ― «новый исполняемый формат» EXE-файлов. Впервые появился в многозадачной MS-DOS версии 4.0, но так и не стал популярным EXE-форматом, используется в старых 16-/32-разрядных версиях Windows или OS/2
IMAGE_OS2_SIGNATURE_LE WORD 454Ch «LE» Linear Executable ― смешанный 16-/32-разрядный формат. Введен на OS/2 версии 2.0 LE-формат больше не используется в приложениях-OS/2, но используется в VxD драйверах под Windows 3.x/9x и на некоторых DOS-расширителях.
WORD 584Ch«LX» Linear eXecutable ― «линейный исполняемый формат» EXE-файлов. Введен в 32-разрядной OS/2 версия 2.0. Используется на OS/2 версии 2.0 и более поздних и на некоторых DOS-расширителях.
IMAGE_NT_SIGNATURE DWORD 00004550h «PE»/0/0 Portable Executable ― «переносимый формат» исполняемых файлов, объектного кода и динамических библиотек, используемый в 32-/64-разрядных версиях операционной системы Microsoft Windows (9X или NT)
Структурно PE-файл состоит из заголовка (header), страничного имиджа (image page) и необязательного оверлея (overlay). Представление PE-файла в памяти называется его виртуальным образом (virtual image) или просто образом, а на диске ― файлом или дисковым образом. Если не оговорено обратное, то под образом всегда понимается виртуальный образ.
Образ характеризуется двумя атрибутами:
  1. адресом базовой загрузки (image base)
  2. размером (image size).
При наличии перемещаемой информации (relocation/fixup table) образ может быть загружен по адресу, отличному от image base и назначаемому непосредственно самой операционной системой. Образ делится на страницы (pages), а файл ― на сектора (sectors). Виртуальный размер страниц/ секторов явным образом задается в заголовке файла и не обязательно должен совпадать с физическим. Системный загрузчик требует от образа непрерывности. На всем протяжении image size начиная от image base не должно присутствовать ни одной «бесхозной» страницы, не принадлежащей ни заголовку, ни секциям ― такой файл не будет загружен. «Бесхозных» секторов в любой части файла может быть сколько угодно. Каждый сектор может отображается на любое количество страниц (по одной странице за раз), но никакая страница не может отображать на один и тот же регион памяти более одного сектора.
Для работы с PE-файлами используются различные схемы адресации:
  1. Физическая адресация (RAW OFFSET)физические адреса они же «сырые указатели» (raw pointers/raw offset) или просто «смещения» (offset), отсчитываются от начала файла;
  2. Виртуальная адресация (VA)виртуальные адреса (virtual address), отсчитываются от начала адресного пространства процесса;
  3. Относительная виртуальная адресация (RVA)относительные виртуальные адреса (relative virtual address), отсчитываются от базового адреса загрузки;
  4. RRA-адресацияRRA-адреса они же «сырые относительные адреса» (Raw Relative Address) или «относительно относительные адреса» (Relative Relative Address). RRA-адреса всегда отсчитываются от стартового адреса своей структуры (в частности, OffsetModuleName задает смещение от начала таблицы диапазонного импорта).
В PE-файлах адреса хранятся в 32-битных указателях. Страничный имидж состоит из одной или нескольких секций. С каждой секцией связано пять атрибутов:
  1. физический адрес начала секции в файле
  2. размер секции в файле
  3. виртуальный адрес секции в памяти
  4. размер секции в памяти
  5. атрибут характеристик секции, описывающий правда доступа, особенности обработки секции системным загрузчиком и так далее.
Хотя, секция «сама решает» ― откуда и куда ей грузиться, но на выбираемые значения наложены ограничения:
  1. Начало каждой секции в памяти/диске всегда совпадает с началом виртуальных страниц/секторов, соответственно. При попытке создать секцию, начинающейся с середины виртуальной страницы ― системный загрузчик откажется обрабатывать такой файл.
  2. Загрузчик не требует, чтобы виртуальный (и частично физический) размер секций был кратен размеру страницы. Загрузчик самостоятельно выравнивает секции, забивая «хвост» секции нулями, так что никакая страница (сектор) не может принадлежать двум и более секциям сразу. Невыровненный в заголовке размер автоматически выравнивается в страничном имидже.
Все секции равноправны, тип каждой секции связан с ее атрибутами (см. «Таблица секций»). Различают четыре атрибута.
Кликните здесь для просмотра всего текста
аппаратный атрибут программный атрибут
Accessible Shared
Writeable Loadable

Служебные структуры данных (таблицы экспорта, импорта, перемещаемых элементов), могут быть расположены в любой секции с подходящими атрибутами доступа.
Кликните здесь для просмотра всего текста
Терминология
  • Секция ― непрерывный набор страниц памяти с одинаковыми атрибутами. Бывают секции кода, данных, ресурсов и так далее. Обычно данные делятся на секции, если предполагается, что они будут использоваться одинаковым образом, то есть например, только для чтения или только для записи. Также, данные могут делиться на секции в зависимости от того, что, представляют из себя, эти данные, например ресурсы или таблица импорта. В общем случае может быть, например 12 секций с одинаковыми атрибутами, и используемые для кода. Мы вправе сами создавать секции, указывая это компиляторам. С другой стороны секция это отдельная сущность PE-файла. Прочтите, что пишут Microsoft в спецификации PE/COFF формата, что такое секция:
    A section is the basic unit of code or data within a PE/COFF file. In an object file, for example, all code can be
    combined within a single section, or (depending on compiler behavior) each function can occupy its own section. With more
    sections, there is more file overhead, but the linker is able to link in code more selectively. A section is vaguely similar
    to a segment in Intel 8086 architecture. All the raw data in a section must be loaded contiguously. In addition, an image
    file can contain a number of sections, such as «.tls» or «.reloc», that have special purposes
  • VA (Virtual Address) ― виртуальный адрес. Адрес в адресном пространстве текущего процесса.
  • RVA (RelativeVirtualAddress) ― относительный виртуальный адрес. При загрузке PE-файла, ОС использует механизм файлового мэппинга (FileMapping). То есть система проецирует данный exe-, dll-, sys- или scr-файл по какому-то адресу в виртуальном адресном пространстве. Адрес начала проекции называется базовым адресом в памяти данного exe-, dll-, sys- или scr-файла. А смещение относительно базового адреса называется ― относительным виртуальным адресом. Например, EXE-файл проецирован по адресу 400000h. Тогда, если PE-заголовок находится по адресу 4000E0h, то RVA PE-заголовка будет 0E0h. В PE-заголовке очень много параметров указываются через RVA. А если RVA начала инструкций в файле равно 1000h, то виртуальный адрес будет равен 401000h учитывая, что база 400000h. Чтобы посчитать относительный виртуальный адрес по данному виртуальному адресу используется следующая формула:
    RVA = VA - IMAGE_OPTIONAL_HEADER.ImageBase
    Иногда возникает необходимость посчитать файловое смещение соответствующее VA или RVA. Если требуется смещение внутри секции, используется следующая формула:
    offset = RVA - IMAGE_SECTION_HEADER.VirtualAddress + IMAGE_SECTION_HEADER.PointerRawData
    Если смещение находится вне секции, то есть в заголовке, таблице секций или еще где-нибудь, то файловое смещение равно RVA.
  • IAT (Import Address Table) ― таблица адресов импорта. Массив двойных слов, содержащие RVA импортируемых функций.
  • INT (Import Name Table) ― таблица импортируемых имен. Массив двойных слов, каждое из которых является RVA на ASCIIZ-строку с импортируемой функцией.
Структура PE-файла
Кликните здесь для просмотра всего текста
Рисунок 1. Схематическое изображение PE-файла.
Все PE-файлы (в том числе и системные драйвера) начинаются с old-exe заголовка за концом которого следует dos-заглушка («ms-dosreal-mode stub program» или просто «stub»). PE-заголовок начинается непосредственно за концом old-exe программ, но может быть расположен в любом месте файла ― в его середине или конце, загрузчик определяет положение PE-заголовка по двойному слову e_lfanew, смещенному на 3Ch байт от начала файла. PE-заголовок представляет собой 18h-байтовую структуру данных, описывающую фундаментальные характеристики файла и содержащую «PE»-сигнатуру, по которой файл и отождествляется.
Непосредственно за концом PE-заголовка, следует опциональный заголовок, специфицирующий структуру страничного имиджа более детально (базовый адрес загрузки, размер образа, степень выравнивания). Важной структурой опционального заголовка является DATA_DIRECTORY, представляющая собой массив указателей на подчиненные структуры данных: таблицы экспорта и импорта, отладочную информацию, таблицу перемещаемых элементов и так далее.
Типичный размер опционального заголовка составляет 0E0h байт, но может меняться в ту или иную сторону, что определяется полнотой занятости DATA_DIRECTORY, а также количеством «мусора» за ее концом. Размер опционального заголовка хранится в PE-заголовке, поэтому эти две структуры связаны друг с другом.
За концом опционального заголовка следует таблица секций. Ни к одному из заголовков она не принадлежит и является самостоятельным заголовком безымянного типа. За концом таблицы секций находится «ничейная область», не принадлежащая ни заголовкам, ни секциям, образованная в результате выравнивания физических адресов секций по кратным адресам. Начиная с физического адреса первой секции, указанного в таблице секций, находится страничный имидж, точнее его упакованный дисковый образ. «Упакованный» в том смысле, что физические размеры секций (с учетом выравнивания) включают в себя лишь инициализированные данные и не содержат ничего лишнего.
Виртуальный размер секций может существенно превосходить физический. В памяти секции всегда упорядочены, чего нельзя сказать о дисковом образе. Кроме «дыр», оставшихся от выравнивания, между секциями могут располагаться оверлеи, при этом порядок следования секций в памяти и на диске совпадает далеко не всегда...
По представлению в памяти секции можно разделить на:
  1. секции постоянно представленные в памяти;
  2. секции появляющиеся в памяти лишь на период загрузки, по завершении которой секции удаляются из памяти;
  3. секции загружающиеся в память частично;
  4. секции незагружающиеся в память. Например, секция с отладочной информацией. Хотя, отладочная информация не обязательно должна оформляться как отдельная секция и, чаще, она добавляется к файлу в виде оверлея.
За концом последней секции обычно расположено некоторое количество «мусора» ― байты оставленные линкером после сборки. Это не оверлей (к нему никогда не происходит обращений), хотя нечто на него похожее. Оверлеев может быть несколько ― системный загрузчик не налагает на это никаких ограничений, но и не предоставляет никаких унифицированных механизмов работы с оверлеями ― программа, создавшая свой оверлей, вынуждена работать с ним самостоятельно.
Описание основных полей PE-файла
Заголовок Реального режима/MS-DOS
Самая первая по счету секция ― не настоящая, это «псевдосекция». Она не имеет имени и содержит заголовки и настроечные таблицы PE-программы. В этой секции сначала размещается обычный заголовок EXE-программы, начинающийся с байтов «MZ». По смещению +3Ch в этом заголовке хранится адрес заголовка PE-программы, начинающегося с байтов «PE».
Первый компонент в PE-файле ― заголовок MS-DOS, тот же заголовок MS-DOS, который используется начиная с MS-DOS версии 2. Главная причина сохранения его нетронутым в самом начале файла ― это то, что когда кто-то попытается загрузить PE-файл в Windows версии 3.1 или более ранней, или под MS-DOS версии 2.0 или более поздней, операционная система сможет прочесть файл и понять, что он не совместим с имеющейся операционной системой. При попытке запустить исполнимый файл от Windows NT под MS-DOS версии 6.0, Вы получите сообщение:
This program cannot be run in DOS mode.
«Эта программа не может быть запущена в DOS». Если бы заголовок MS-DOS не был включен, как первая часть PE-файла, операционная система могла бы просто сбойнуть при попытке запуска такого файла, и вывести на экран:
The name specified is not recognized as an internal or external command, operable program or batch file.
«Указанное имя не является внешней или внутренней командой, исполнимой программой или командным файлом». При отсутствии сигнатуры «MZ» функции MiCreateImageFileMap и NtCreateSection вернут STATUS_INVALID_IMAGE_NOT_MZ. Когда линкуется приложение для Windows, линкер подставит в приложение «stub-программу по умолчанию», называемую WINSTUB.EXE. Вы можете задать линкеру свою собственную DOS-программу вместо WINSTUB, указав ее с помощью ключа STUB при линковке вашего файла. DOS-заголовок занимает
первые 64 байта PE-файла (реально больше 64 байт). Структура, определяющая его содержимое, описана ниже:

Кликните здесь для просмотра всего текста
смещение от начала файларазмерназваниекомментарий
+00h WORD e_magic магическое число
+02h WORD e_cblp количество байт на последней странице файла
+04h WORD e_cp количество страниц в файле
+06h WORD e_crlc Relocations
+08h WORD e_cparhdr размер заголовка в параграфах
+0Ah WORD e_minalloc Minimum extra paragraphs needed
+0Ch WORD e_maxalloc Maximum extra paragraphs needed
+0Eh WORD e_ss начальное ( относительное ) значение регистра SS
+10h WORD e_sp начальное значение регистра SP
+12h WORD e_csum контрольная сумма
+14h WORD e_ip начальное значение регистра IP
+16h WORD e_cs начальное ( относительное ) значение регистра CS
+18h WORD e_lfarlc указатель на таблицу перемещения DOS-программы
+1Ah WORD e_ovno количество оверлеев
+1Ch WORD e_res[4] Зарезервировано
+24h WORD e_oemid OEM identifier (for e_oeminfo)
+26h WORD e_oeminfo OEM information; e_oemid specific
+28h WORD e_res2[10] Зарезервировано
+3Ch DWORD e_lfanew указатель на новый exe-заголовок

[old-exe] e_magic
Первое поле, e_magic, так называемое магическое число. Это поле используется для идентификации типа файла, совместимого с MS-DOS. Все DOS-совместимые исполнимые файлы имеют сигнатуру 54ADh, представляющую в ASCII-символах строку «MZ». По этой причине на DOS-заголовок часто ссылаются, как на MZ-заголовок. Большинство полей DOS-заголовка имеет значение только для операционной системы MS-DOS, для Windows NT только одно поле в этой структуре представляет интерес. Это поле e_lfanew, четырехбайтовое смещение в файле, указывающее на расположение структуры IMAGE_NT_HEADERS. У большинства файлов, PE-заголовок расположен сразу за DOS-заголовком ― между ними, как правило, расположено только тело программы DOS-заглушки (ms-dos real-mode stub program). Если e_magic равно «MZ», загрузчик приступает к поиску «PE» сигнатуры, в противном случае, поведение загрузчика становится неопределенным.
[old-exe] e_cparhdr
Размер old-exe заголовка в параграфах (1 параграф равен 200h байтам). Минимальный размер заголовка составляет 1 параграф, а максимальный ― ограничен размером самой MS-DOS заглушки, то есть если он будет больше поля e_lfanew, файл может и не загрузиться.
[old-exe] e_lfarlc
Указатель на таблицу перемещения DOS-программы. Если здесь значение 40h или больше, то перед нами Windows или OS/2 программа и по смещению 3Ch стоит указатель на NE- или LE- или PE-заголовок.
[old-exe] e_lfanew
Смещение PE-заголовка в байтах от начала файла. Должно указывать на первый байт PE-сигнатуры «PE», выровненной по границе двойного слова, иначе ― STATUS_INVALID_IMAGE_FORMAT; если сумма image base и e_lfanew выходит за пределы отведенного загрузчиком адресного пространства, такой файл не грузится. Наиболее часто встречаются значения в диапазоне 0C0h..0xF8h.
Rich Signature
содержит информацию о версиях компилятора/линкера и, возможно, о каких-то флагах/опциях компиляции/линковки. И не хранит идентификаторы железа и т.п., поэтому если прога будет собрана на разных машинах с одинаковым софтом (одинаковые версии, сервис-паки, длл и т.д.), то Rich Signature этих файлов совпадут.
Кликните здесь для просмотра всего текста
Rich Signature до расшифровки
Для расшифровки использую hiew32. Нажимаю F4 (Mode), выбираю Hex, нажимаю F3 (Edit), встаю на строку, с которой начну дешифрацию, затем F8 (Xor), получаю приглашение «Enter XOR mask» ввожу в строке Hex «93 D6 3E 94», нажимаю на Enter и несколько раз нажимаю на F8
Кликните здесь для просмотра всего текста

Rich Signature после расшифровки
Со смещения 80h идут байты 0D7h, 0B7h, 50h, 0C7h. Это строка «DanS», поксоренная некоторым числом (xmask). Далее подряд следуют 3 xmask (93h, 0D6h, 3Eh, 94h). Маска представляет собой 32-битное число, вычисленное некоторым алгоритмом. Затем идут данные (xdata), поксоренные на xmask (в диапазоне смещений от 90h до 0C7h). Для каждой Rich Signature может быть свой набор данных и их количество. После данных располагаются строка «Rich», xmask и нули (в данном примере их 8, но может быть как больше, так и меньше. Количество нулей кратно восьми).
Структура IMAGE_NT_HEADERS
В памяти PE-заголовк (вместе со вмести остальными заголовками) всегда располагается перед первой секцией, расстояние между виртуальным адресом первой секции и концом заголовка должно быть меньше, чем Section Alignment. На диске PE-заголовок может быть расположен в любом месте файла, например, в его середине или конце (то есть между началом файла и первым байтом PE-заголовка могут находиться одна или несколько секций).
  1. SizeOfHeader должно быть равно действительному размеру PE-заголовка плюс значение в e_lfanew;
  2. FirstSection.RVA http://www.cyberforum.ru/cgi-bin/latex.cgi?\geq SizeOfHeaders.
После DOS-программы идет структура, которая называется IMAGE_NT_HEADERS.
Эта структура определена так:
Кликните здесь для просмотра всего текста
Код
   IMAGE_NT_HEADERS STRUCT
	Signature	dd ?
	FileHeader	IMAGE_FILE_HEADER	<>
	OptionalHeader	IMAGE_OPTIONAL_HEADER32	<>
   IMAGE_NT_HEADERS ENDS

Почти все определения структур PE-файла можно узнать из файла WINNT.H, который поставляется вместе со средой
программирования. Первый элемент IMAGE_NT_HEADERS ― сигнатура PE-файла. Для PE-файлов она должна иметь значение IMAGE_NT_SIGNATURE. Далее идет структура, которая называется файловым заголовком и определенная как IMAGE_FILE_HEADER. Файловый заголовок содержит наиболее общие свойства для данного PE-файла. После файлового заголовка идет опциональный заголовок ― IMAGE_OPTIONAL_HEADER32. Он содержит специфические параметры данного PE-файла. В конце опционального заголовка содержится массив элементов DataDirectory. Он служит для доступа к директориям, которые могут быть секциями, а могут и не быть. После опционального
заголовка начинается таблица секций. В ней содержится информация о каждой секции. После таблицы секций идут исходные данные для секций. В конец PE-файла можно записать любую информацию и от этого функционирование программы не измениться (если там не присутствует проверка контрольной суммы).
Файловый заголовок
20 байт файлового заголовка начинаются в PE-файле после сигнатуры IMAGE_NT_SIGNATURE. Файловый заголовок содержит наиболее общую информацию о данном файле. Главные поля в файловом заголовке ― это количество секций и размер опционального заголовка. Остальные нужны очень редко или не нужны совсем. В WINNT.H файловый заголовок определен следующим образом:
[center]
Кликните здесь для просмотра всего текста
смещение от «PE»размерназваниекомментарий
+04h WORD Machine Тип центрального процессора
+06h WORD NumberOfSections Количество секций NumberOfSections <= 96
+08h DWORD TimeDateStamp Информация о времени, когда был собран данный PE-файл
+0Ch DWORD PointerToSymbolTable Указатель на размер отладочной информации
+10h DWORD NumberOfSymbols Указатель на COFF-таблицу символов PE-формата
+14h WORD SizeOfOptionalHeader Размер опционального заголовка
+16h +22 WORD Characteristics

[IMAGE_FILE_HEADER] Machine
Тип центрального процессора, под который скомпилирован файл. Поле используется для идентификации платформы, для которой приложение было построено, например, DEC Alpha, MIPS R4000, Intel x86, или другие типы процессоров. Система использует эту информацию, чтобы быстро определить, что представляет собой данный файл без углубления в дальнейшие подробности его содержимого. Возможные значения приведены ниже.
Кликните здесь для просмотра всего текста
название значение [b]комментарий
IMAGE_FILE_MACHINE_UNKNOWN 0 Неизвестный тип процессора. Windows не запустит такой файл
IMAGE_FILE_MACHINE_I386 14Ch Intel 386
14Dh Intel 486
14Eh Intel Pentium
160h MIPS big-endian
IMAGE_FILE_MACHINE_R3000 162h MIPS Mark I little-endian (R2000, R3000)
163h MIPS Mark II little-endian (R6000)
IMAGE_FILE_MACHINE_R4000 166h MIPS Mark III little-endian (R4000)
IMAGE_FILE_MACHINE_R10000 168h MIPS little-endian
IMAGE_FILE_MACHINE_WCEMIPSV2 169h MIPS little-endian WCE v2
IMAGE_FILE_MACHINE_ALPHA 184h Alpha_AXP
IMAGE_FILE_MACHINE_POWERPC 1F0h IBM PowerPC Little-Endian
IMAGE_FILE_MACHINE_SH3 1A2h SH3 little-endian
IMAGE_FILE_MACHINE_SH3E 1A4h SH3E little-endian
IMAGE_FILE_MACHINE_SH4 1A6h SH4 little-endian
IMAGE_FILE_MACHINE_ARM 1C0h ARM (Advanced RISC Machine) Little-Endian
IMAGE_FILE_MACHINE_THUMB 1C2h
IMAGE_FILE_MACHINE_IA64 200h Intel 64
IMAGE_FILE_MACHINE_MIPS16 266h MIPS
IMAGE_FILE_MACHINE_MIPSFPU 366h 
IMAGE_FILE_MACHINE_MIPSFPU16 466h 
IMAGE_FILE_MACHINE_ALPHA64 284h ALPHA64
IMAGE_FILE_MACHINE_AXP64 284h 
IMAGE_FILE_MACHINE_CEF 0C0EFh 
Я попытался использовать 14Dh и 14Eh, упоминающиеся в pe.txt от LUEVELSMEYER, и присутствующие в hiew32 (Intel486, Pentium), но Windows NT отказалась запустить такую программу.
Cannot execute D:\MASM\MyProjects\msgbox.exe
ОС Windows поддерживает только две архитектуры и все они ― процессоры Intel (IA32 и IA64). Исходя из этого, только два значения считаются корректными в PE-файле IMAGE_FILE_MACHINE_IA64 и IMAGE_FILE_MACHINE_I386. Если подставить что-либо другое, загрузчик откажется загружать данный файл. Для 32х разрядных операционных систем ― значение единственное ― IMAGE_FILE_MACHINE_I386.
[IMAGE_FILE_HEADER] NumberOfSections
Количество секций. Поле содержит количество секций, или, более точно, количество заголовков секций и количество тел секций, имеющихся в исполнимом PE-файле ― для облегчения получения информации по ним. Каждые заголовок и тело секции расположены в файле последовательно, так что число секций необходимо, чтобы определить, где заканчиваются заголовки и тела секций. Максимальное количество секций определяется особенностями реализации конкретного лоадера. Количество секций должно быть сведено к минимуму. Если заявленное количество секций меньше числа записей в Section Table, то остальные секции просто не грузятся, но в целом такой файл обрабатывается вполне нормально. Значение «Количество секций» должно быть верным. Фактически означает число элементов в таблице секций.

[IMAGE_FILE_HEADER] TimeDateStamp
Информация о времени, когда был собран данный PE-файл. Это значение равно количеству секунд прошедших с 1 января 1970 года до времени создания файла. В WinAPI есть функция gmtime, которая переводит время из секунд в удобочитаемый вид. Функция берет указатель на TimeDateStamp и заполняет структуру SYSTEMTIME.
Кликните здесь для просмотра всего текста
Код
SYSTEMTIME* Time = gmtime(&x);
// где х ― значение поля TimeDateStamp
SYSTEMTIME STRUCT
  wYear             WORD      ?;Год (минус 1900) 
  wMonth            WORD      ?;Месяц (0-11) 
  wDayOfWeek        WORD      ?;День недели (0-6; воскресенье = 0)
  wDay              WORD      ?;День месяца (1-31) 
  wHour             WORD      ?;Часы (0-23)
  wMinute           WORD      ?;Минуты
  wSecond           WORD      ?;Секунды
  wMilliseconds     WORD      ?
SYSTEMTIME ENDS
Можно использовать функцию gmtime из стандартной библиотеки С (time.h), переводящую время из секунд в удобочитаемый вид:
Кликните здесь для просмотра всего текста
C
1
2
3
4
DWORD TimeDateStamp = 0x4C9FABF5;
    struct tm *xtm = gmtime((const long*)&TimeDateStamp);
    printf("day = %d\nmonth = %d\nyear = %d\n", xtm->tm_mday, 
xtm->tm_mon, (xtm->tm_year + 1900));
В принципе, здесь можно сгенерировать любое число, например, в диапазоне [0x40000000..0x4C000000] (2004-2020 гг.).
[IMAGE_FILE_HEADER] PointerToSymbolTable/NumberOfSymbols
Указатель на размер отладочной информации в объектных файлах. В настоящее время не используется. Линкеры
устанавливают оба поля в ноль, отладчики, дизассемблеры и системный загрузчик игнорируют его. PointerToSymbolTable ― указатель на COFF-таблицу символов PE-формата. Эту же информацию можно найти в элементе массива DataDirectory с индексом IMAGE_DIRECTORY_ENTRY_DEBUG. Мы может размещать в этом поле любое значение. NumberOfSymbols ― количество символов в COFF-таблице символов. Также может принимать любое значение.
[IMAGE_FILE_HEADER] SizeOfOptionalHeader
Размер опционального заголовка. Опциональный заголовок следует сразу же за файловым заголовком (IMAGE_FILE_HEADER). Должен указывать на первый байт Section Table (&amp;Section Table = e_lfanew + 18h + SizeOfOptionalHeader), где 18h ― sizeof(IMAGE_FILE_HEADER). Если это не так, файл не загружается. Некоторые загрузчики вычисляют указатель на Section Table отталкиваясь от NumberOfRvaAndSizes. Размер опционального заголовка зависит от массива DataDirectory, а именно от количества элементов в нем. Обычно там 16 элементов. Это поле проверяется загрузчиком и должно быть правильным.
[IMAGE_FILE_HEADER] Characteristics
Атрибуты специфичные для данного PE-файла. Поле Characteristics ― 16-битное поле и каждый установленный бит представляет из себя отдельный флаг. Если (Characteristics &amp; IMAGE_FILE_EXECUTABLE_IMAGE) == 0, файл не грузится, то есть первый, считая от нуля, бит характеристик обязательно должен быть установлен. У динамических библиотек должно быть установлено как минимум два атрибута: IMAGE_FILE_EXECUTABLE_IMAGE и IMAGE_FILE_DLL, то же самое относится и к исполняемым файлам, экспортирующим одну или более функций. Если атрибут IMAGE_FILE_DLL установлен, но экспорта нет, исполняемый файл запускаться не будет. Поле Characteristics содержит и другие специфические характеристики файла, например, признак наличия отдельного файла с отладочной информацией. Можно удалить отладочную информацию из самого PE-файла и cохранить ее в отдельном отладочном файле (.DBG) для использования отладчиком. При отладке отладчику нужно знать, содержится ли отладочная информация в отдельном файле или нет, и удалена ли она из исполнимого файла. Отладчик определяет это, просматривая содержимое исполнимого файла. Для устранения необходимости просматривать содержимое всего файла, было введено поле, содержащее признак удаления из файла отладочной информации (IMAGE_FILE_DEBUG_STRIPPED). Отладчик может быстро определить, присутствует ли в файле отладочная информация.
Большинство атрибутов не используются в настоящее время, так как PE-формат создан в 1993 году. Определены следующие значения:
Кликните здесь для просмотра всего текста
название величина комментарий
IMAGE_FILE_RELOCS_STRIPPED 0001h В файле отсутствует информация о базовых поправках. Этот флаг не используется в исполняемых файлах. Вместо этого информация о базовых поправках храниться в каталоге, на который указывает элемент в массиве DataDirectory с индексом IMAGE_DIRECTORY_ENTRY_BASERELOC.
IMAGE_FILE_EXECUTABLE_IMAGE 0002h Файл является исполняемым (то есть не содержит нераспознанных внешних ссылок). Если файл является исполняемым, то он не является объектным файлом или библиотекой.
IMAGE_FILE_LINE_NUMS_STRIPPED 0004h В файле отсутствуют номера строк. Это значение не используется в исполняемых файлах.
IMAGE_FILE_LOCAL_SYMS_STRIPPED 0008h Локальные символы отсутствуют в файле. Это значение не используется в исполняемых файлах.
IMAGE_FILE_AGGRESIVE_WS_TRIM 0010h Этот флаг установлен, если операционная система ограничивает программу памятью, сбрасывая данные приложения в страничный файл. Этот флаг устанавливается для приложений, которые большую часть своего времени ждут, лишь очень редко пробуждаясь.
IMAGE_FILE_LARGE_ADDRESS_AWARE 0020h Флаг, чтобы приложение могло работать с объемом памяти больше 2 или 3 Гб (в зависимости от загрузочного параметра).
IMAGE_FILE_BYTES_REVERSED_LO 0080hЭти флаги устанавливаются если порядок байт в конце файла, отличен от порядка байт для текущей архитектуры. Так как порядок байт в процессорах Intel одинаковый, то этот параметр в данное время не используется.
IMAGE_FILE_BYTES_REVERSED_HI 8000h 
IMAGE_FILE_32BIT_MACHINE 0100h Этот флаг установлен, если предполагается, что машина 32-разрядная. Вероятно, если файл будет собран при помощи 64-разраного линкера, то этот флаг не будет установлен.
IMAGE_FILE_DEBUG_STRIPPED 0200h Отладочная информация отсутствует в файле. Этот параметр не используется для исполняемых файлов.
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0400h Этот флаг установлен, если приложение может не запуститься с переносного носителя, дискеты или CD-ROM. В этом случае ОС переносит данные исполняемый файл в файл подкачки и считывает его оттуда. Но этот флаг в данный момент избыточен, так как ОС сама переносит исполняемый файл в файл подкачки, если он находиться на подобном съемном носителе.
IMAGE_FILE_NET_RUN_FROM_SWAP 0800h Флаг установлен, если приложение может не запуститься по сети. Но этот флаг в данный момент избыточен, так как ОС сама переносит исполняемый файл в файл подкачки, если он находиться на общем сетевом ресурсе.
IMAGE_FILE_SYSTEM 1000h Этот флаг установлен, если данный файл является системным, подобно драйверу. В настоящее время не используется.
IMAGE_FILE_DLL 2000h Данный файл ― динамически подключаемая библиотека (DinamicLinkLibrary). Каждая DLL обязана иметь этот флаг, иначе она не загрузится. Этот флаг может использоваться EXE, и при этом быть корректным исполняемым файлом.
IMAGE_FILE_UP_SYSTEM_ONLY 4000h Этот флаг установлен, если приложение не предназначено для многопроцессорных платформ.
Для exe можно смело использовать значение 0x10F (для dll – 0x210E). Остальные атрибуты не столь фатальны и под Windows NT/9x безболезненно переносят любые значения. Взять хотя бы IMAGE_FILE_BYTES_REVERSED_LO и IMAGE_FILE_BYTES_REVERSED_HI, описывающие порядок следования байт в слове. Какому абстрактному состоянию процессора соответствует одновременная установка обоих атрибутов? И какие действия должен предпринять загрузчик, если установленный порядок следования байт будет отличаться от поддерживаемого процессором? Операционные системы от Microsoft игнорируют эти атрибуты за ненадобностью. То же самое относится и к атрибуту IMAGE_FILE_32BIT_MACHINE, которым по умолчанию награждаются все 32-разрядные файлы.
Флаг IMAGE_FILE_DEBUG_STRIPPED, указывает на отсутствие отладочной информации и запрещает отладчикам работать с ней даже тогда, когда она есть. Отладочная информация привязана к абсолютным смещениям, отсчитываемым от начала файла и при внедрении в файл чужеродного кода путем его расширения отладочная информация перестает соответствовать действительности и поведение отладчиков становится крайне неадекватным. Для решения проблемы существуют три пути:
  1. скорректировать отладочную информацию (но для этого нужно знать ее формат);
  2. отрезать отладочную информацию от файла (но для этого ее надо найти, кроме того за концом файла может быть расположен посторонний оверлей);
  3. установить флаг IMAGE_FILE_DEBUG_STRIPPED. Последний способ самый простой, но и самый надежный.
Флаг IMAGE_FILE_RELOCS_STRIPPED, запрещает перемещать файл когда релокаций нет. Если они есть, загрузчик игнорирует этот флаг. Служебные структуры PE-файла используют только относительную адресацию и потому любой PE-файл перемещаем. Вся загвоздка в программном коде, активно использующем абсолютную адресацию (так устроены современные компиляторы). Технически ничего не стоит создать PE-файл, не содержащий перемещаемых элементов и способный работать по любому адресу. Таким образом, возникает неоднозначность: то ли перемещаемых элементов нет, потому что файл полностью перемещаем и fixup'ы ему не нужны, то ли они просто недоступны и перемещать такой файл ни в коем случае нельзя. По умолчанию ms link версии и 6.0 и старше внедряет перемещаемые элементы только в DLL, а исполняемые файлы неперемещаемые.
Опциональный заголовок
Следующие 0E0h (224) байта в исполнимом файле занимает опциональный заголовок. «Опциональным» заголовок называется, потому что, если рассматривать его в общем стандарте PE/COFF файлов, то для объектных файлов COFF-формата он отсутствует. Для исполняемых файлов этот заголовок является обязательным. Опциональный заголовок содержит более полезную информацию об исполнимом файле, такую, как начальный размер стека, адрес точки входа программы, предпочтительный адрес загрузки, версию операционной системы, информацию о выравнивании сегментов и так далее. Размер опционального заголовка не является фиксированным и чтобы узнать его надо обратиться к файловому заголовку. Структура IMAGE_OPTIONAL_HEADER, представляющая опциональный заголовок, выглядит так:
Кликните здесь для просмотра всего текста
смещение от «PE»размерназваниекомментарий
 Стандартные поля NT
+18h WORD Magic Состояние отображаемого файла
+1Ah BYTE MajorLinkerVersion Содержат версию линкера, создавшего данный файл.
+1Bh BYTE MinorLinkerVersion 
+1Ch DWORD SizeOfCode Суммарный размер секций кода
+20h DWORD SizeOfInitializedData Суммарный размер инициализированных данных
+24h DWORD SizeOfUninitializedData Суммарный размер неинициализированных данных
+28h DWORD AddressOfEntryPoint Относительный адрес точки входа, отсчитываемый от начала Image Base. Из всех стандартных полей, поле AddressOfEntryPoint является наиболее интересным для формата PE файлов. Это поле содержит адрес точки входа приложения, и, что, вероятно, более важно для для хакеров, местоположение конца Import Address Table (таблицы импортируемых адресов ― IAT).
+2Ch DWORD BaseOfCode Относительные базовые адреса кодовой секции. Относительное смещение сегмента кода («.text» сегмент) в загруженном файле.
+30h DWORD BaseOfData Относительные базовые адреса секции данных. Относительное смещение сегмента неинициализированных данных («.bss» сегмент) в загруженном файле.
   дополнительные поля NT
+34h DWORD ImageBase Базовый адрес загрузки страничного имиджа. Предпочтительный адрес в адресном пространстве процесса для загрузки исполнимого файла. Линкер подставляет значение по умолчанию 400000h, но это значение можно изменить с помощью ключа BASE. ImageBase должно быть выравнено на границу 10000h иначе ― STATUS_INVALID_IMAGE_FILE_FORMAT
+38h DWORD SectionAlignment Кратность выравнивания секций в памяти. Сегменты загружаются в адресное пространство процесса последовательно, начиная с ImageBase. SectionAlignment предписывает минимальный размер, который сегмент может занять при загрузке ― так что сегменты оказываются выровненными по границе SectionAlignment. Выравнивание сегмента не может быть меньше размера страницы ( в настоящий момент 4096 байт на платформе x86), и должно быть кратно размеру страницы, как предписывает поведение менеджера виртуальной памяти Windows NT. 4096 байт являются значением по умолчанию, но может быть установлено другое значение, при использовании ключа ALIGN
  1. Если SectionAlignment < 1000h:
    • Единственная подсекция, включающая в себя весь файл, атрибуты защиты - MM_EXECUTE_WRITECOPY;
    • для каждой секции в файле должно быть выполнено следующее:
      • сложение PointerToRawData+SizeOfRawData не вызывает (беззнакового) переполнения;
      • PointerToRawData == VirtualAddress;
      • VirtualSize <= SizeOfRawData (STATUS_INVALID_IMAGE_FORMAT).
  2. Если SectionAlignment >= 1000h:
    • первая подсекция - PE-заголовок, размер - SizeOfHeaders, выравненный вверх на границу SectionAlignment, атрибуты защиты - MM_READONLY;
    • остальные подсекции - это PE-секции, для каждой из которых проверяется следующее:
      • SectionVirtualSize = ALIGN_UP((VirtualSize==0)?SizeOfRawData:VirtualSize, SectionAlignment);
      • сложение PointerToRawData с SizeOfRawData не вызывает переполнения; (STATUS_INVALID_IMAGE_FORMAT, как и везде далее);
      • VirtualAddress должен совпадать с текущим рассматриваемым RVA, то есть
        1. VirtualAddress выравнен на SectionAlignment
        2. все подсекции (начиная с заголовка) в памяти идут последовательно;
      • VirtualSize ненулевой либо SizeOfRawData ненулевой;
      • у PointerToRawData отбрасываются младшие 9 бит (округление вниз до границы 0x200);
      • атрибуты защиты берутся из атрибутов секции;
      • если SizeOfRawData ненулевой, то PointerToRawData + SizeOfRawData <= размера файла.
+3Ch DWORD FileAlignment Кратность выравнивания секций на диске. Минимальная гранулярность сегментов в исполнимом файле до его загрузки. Пояснение: линкер дополняет нулями тела сегментов, (сырые данные сегментов), чтобы их минимальный размер был кратен FileAlignment в файле. По умолчанию линкер выравнивает содержимое тел сегментов по границе 200h байт. Это значение должно быть степенью двойки между 512 и 65536.
+40h WORD MajorOperatingSystemVersion Означает старшую часть версии операционной системы Windows NT, в настоящее время равной 4 для Windows NT версии 4.0.
+42h WORD MinorOperatingSystemVersion Означает младшую часть версии операционной системы Windows NT, в настоящее время равной 0 для Windows NT версии 4.0.
+44h WORD MajorImageVersion Используется для обозначения старшей части версии приложения; в Microsoft Excel версии 4.0 значение этого поля было бы 4.
+46h WORD MinorImageVersion Используется для обозначения младшей части версии приложения; в Microsoft Excel версии 4.0 значение этого поля было бы 0.
+48h WORD MajorSubsystemVersion Означает старшую часть версии Win32-подсистемы Windows NT, в настоящее время равной 4 для Windows NT версии 4.0.
+4Ah WORD MinorSubsystemVersion Означает младшую часть версии Win32-подсистемы Windows NT, в настоящее время равной 0 для Windows NT версии 4.0.
+4Ch DWORD (reserved) Значение неизвестно, в настоящее время системой не используется и устанавливается линкером в 0.
+50h DWORD SizeOfImage Размер страничного имиджа, выровненный на величину Section Alignment. Содержит объем адресного пространства, зарезервированного в адресном пространстве процесса для загружающегося исполнимого файла. Это число сильно зависит от SectionAlignment. Простой пример: Представим, что система имеет фиксированный размер страницы 4096 байт. Если мы имеем исполнимый файл из 11 сегментов, каждый из которых меньше 4096 байт, выравненных по границе 65536 байт, поле SizeOfImage должно быть установлено
11 * 65536 = 720896 (176 страниц).
Тот же самый файл, построенный с выравниванием 4096 байт, в результате будет иметь размер в памяти
11 * 4096 = 45056 (11 страниц)
для поля SizeOfImage. Это простой пример, в котором каждый сегмент требует менее одной страницы памяти. В действительности линкер определяет точное значение SizeOfImage, подсчитывая требуемое место для каждого сегмента. Сначала он определяет размер каждого сегмента, затем округляет это число, чтобы оно стало кратно размеру страницы, и наконец, он вычисляет количество страниц, чтобы их размер стал кратен SectionAlignment. Эти размеры далее суммируются по каждому сегменту.
+54h DWORD SizeOfHeaders Суммарный размер всех заголовков, сообщающий загрузчику, сколько байт читать от начала файла. Это поле содержит размер места, занимаемого всеми заголовками файла, включая заголовок MS-DOS, заголовок PE файла, опциональный заголовок PE файла, и заголовки всех сегментов. Тела сегментов начинаются по смещению в файле, хранимому в этом поле. SizeOfHeaders < SizeOfImage (STATUS_INVALID_IMAGE_FORMAT);
+58h DWORD CheckSum Контрольная сумма файла, используется для проверки целостности исполнимого файла во время загрузки. Это поле устанавливается и проверяется линкером.
+5Ch WORD Subsystem Требуемая подсистема, которую операционная система должна предоставить файлу
+5Eh WORD DllCharacteristics Определяет набор флагов, указывающих ― при каких условиях точка входа в DLL получает управление. Флаги, используемые в .DLL для указания наличия точки входа для старта/завершения процесса и его потоков (DllMain).
+60h DWORD SizeOfStackReserveSizeOfStackReserve Объем зарезервированной памяти под стек в байтах SizeOfStackCommit Объем выделенной памяти под стек в байтах SizeOfHeapReserve Объем зарезервированной памяти под кучу в байтах. SizeOfHeapCommit Объем выделенной памяти под кучу в байтах. Эти поля контролируют объем адресного пространства, зарезервированного и выделенного для стека и кучи по умолчанию. По умолчанию, и для стека ,и для общей кучи зарезервировано 16 страниц, и выделено по одной странице. Эти значения могут устанавливаться ключами STACKSIZE и HEAPSIZE соответственно.
+64h DWORD SizeOfStackCommit 
+68h DWORD SizeOfHeapReserve 
+6Ch DWORD SizeOfHeapCommit  
+70h DWORD LoaderFlags Указывает отладчику остановиться после загрузки, перейти к отладке после загрузки, или, значение по умолчанию, просто запустить файл.
+74h DWORD NumberOfRvaAndSizes Это поле содержит размер массива DataDirectory, расположенного далее. Важно отметить, что это поле содержит именно размер этого массива, а не количество элементов в нем.
+78h DWORD DataDirectory Каталог данных ― содержит указатели на другие важные компоненты PE файла. В действительности, это есть ни что иное, как массив структур IMAGE_DATA_DIRECTORY, расположенных в конце опционального заголовка. В настоящее время формат PE файлов определяет 16 возможных типов каталогов данных, 11 из которых используются чаще всего.
Опциональный заголовок абстрактно делится на две части на «стандартные поля» и «дополнительные поля NT».
  • Стандартные поля ― имеющиеся и в Common Object File Format (общий формат объектных файлов ― COFF), который используется большинством исполнимых файлов UNIX. Несмотря на то, что стандартные поля имеют такие же названия, как определено в COFF, в действительности Windows NT использует некоторые из них совершенно для других целей, нежели предписано COFF.
  • Дополнительные поля, добавленные в формате PE файлов Windows NT, предоставляют поддержку загрузчику для специфичного поведения процессов Windows NT.
Стандартные поля
[IMAGE_OPTIONAL_HEADER] Magic
Состояние отображаемого файла. Это слово служит, чтобы проверить для какой версии спецификации PE этот опциональный заголовок. Возможные значения:
Кликните здесь для просмотра всего текста
название значение комментарий
IMAGE_NT_OPTIONAL_HDR32_MAGIC 10Bh Для спецификации PE32
IMAGE_NT_OPTIONAL_HDR64_MAGIC 20Bh Для спецификации PE64
IMAGE_ROM_OPTIONAL_HDR_MAGIC 107h Исполняемый файл после проекции его загрузчиком будет только для чтения. В настоящее время не используется.
Для 32-разрядных ОС есть одно возможное значение ― IMAGE_NT_OPTIONAL_HDR32_MAGIC Если здесь будет что-то отличное от 10Bh (сигнатура исполняемого отображения), файл не загрузится. PE64-файлам соответствует сигнатура 20Bh (все адреса у них 64-разрядные), а в остальном они ведут себя как 32-разряные PE-файлы.
[IMAGE_OPTIONAL_HEADER] MajorLinkerVersion/MinorLinkerVersion
Старшее и младшее слово версии линкера, создавшего данный файл. Может быть любым.
[IMAGE_OPTIONAL_HEADER] SizeOfCode/SizeOfInitializedData/SizeOfUninitializedData
Суммарный размер секций кода, инициализированных и неинициализированных данных (то есть секций, имеющих атрибуты IMAGE_SCN_CNT_CODE, IMAGE_SCN_CNT_INITIALIZED_DATA и IMAGE_SCN_CNT_UNINITIALIZED_DATA). Никем не проверяется и может принимать любые, в том числе и заведомо бессмысленные значения. Разные линкеры заполняют эти поля по-разному: одни берут физический размер секций на диске, другие ― виртуальный размер в памяти, выровненный по границе Section Alignment, причем алгоритм определения принадлежности секции к тому или иному типу не стандартизирован.
[IMAGE_OPTIONAL_HEADER] AddressOfEntryPoint
Относительный адрес точки входа, отсчитываемый от начала Image Base. Может указывать в любую точку адресного пространства, в том числе и не принадлежащую страничному имиджу (например, направленную на какую-нибудь функцию внутри ядра или dll). Если точка входа направлена на заголовок или последнюю секцию файла, антивирусы считают файл зараженным вирусом, во избежание недоразумений точку входа лучше всего располагать в первой секции файла, которой по обыкновению является кодовая секция .text. Для exe-файлов точка входа соответствует адресу, с которого начинают считываться инструкции для выполнение и не может быть равна нулю, а для динамических библиотек ― функции диспетчера, условно называемая DllMain, хотя при компоновке dll с
настройками по умолчанию компоновщик внедряет стартовый код, перехватывающий на себя управление и вызывающий «настоящую» DllMain по своему желанию. DllMain вызывается при следующих обстоятельствах ― загрузка/выгрузка dll и создание/уничтожение потока, если точка входа в dll равна нулю, функция DllMain не вызывается. Адрес является RVA. Чтобы указать на адрес ниже базового можно использовать отрицательные значения, то есть в дополнительном коде. По-другому это называется ― целочисленное переполнение. Чтобы отличить dll от обычных файлов, следует проанализировать поле характеристик (см. «Characteristics»). Опираться на наличие/отсутствие таблицы экспорта ни в коем случае нельзя, поскольку экспортировать функции могут не только
динамические библиотеки, но и исполняемые файлы. Иногда встречаются динамические библиотеки, не экспортирующие ни одной функции.
[IMAGE_OPTIONAL_HEADER] BaseOfCode/BaseOfData
BaseOfCode ― RVA откуда начинаются секция(и) кода исполняемого файла. Может быть любым значением, так как не используются загрузчиками. Но если это значение неправильное это может вызвать подозрение у разных отладчиков. BaseOfData ― RVA откуда начинаются секция(и) данных исполняемого файла. Может быть любым значением, так как не используются загрузчиками. Никем не проверяется.
Дополнительные поля Windows NT
[IMAGE_OPTIONAL_HEADER] ImageBase
Базовый адрес загрузки страничного имиджа, измеряемый в абсолютных адресах, отсчитываемых от начала сегмента или, в терминологии оригинальной спецификации, preferred address (предпочтительный адрес загрузки). При наличии таблицы перемещаемых элементов, файл может быть загружен по адресу, отличному от указанного в заголовке. Это происходит в тех случаях, когда требуемый адрес занят системой, динамической библиотекой или загрузчику захотелось что-то подвигать. При запуске PE-файла он будет отображен по частям, начиная с некоторого адреса в памяти. Адрес отображения называется базовым адресом для данного файла. В данном поле храниться базовый адрес PE-файла. Этот файл естественно является VA. От него отсчитываются все RVA. Еcли файл не загружается по каким-то причинам (по этому адресу помять уже зарезервирована) по данному адресу, то загрузчику необходимо применять базовые поправки. Обычно файл загружается по базовому адресу и базовые поправки не нужны. Это позволяет использовать базовые поправки в своих целях. Для компоновщиков, по умолчанию устанавливается базовый адрес 400000h.
[IMAGE_OPTIONAL_HEADER] FileAlignment/SectionAlignment
Кратность выравнивания секций на диске и в памяти. Официально о кратности выравнивания известно лишь то, что она представляет собой степень двойки, причем:
  1. SectionAlignment должно быть http://www.cyberforum.ru/cgi-bin/latex.cgi?\geq1000h байт;
  2. FileAlignment должно быть http://www.cyberforum.ru/cgi-bin/latex.cgi?\geq200h байт;
  3. Значение SectionAlignment должно быть http://www.cyberforum.ru/cgi-bin/latex.cgi?\geq значения FileAlignment.
Если хотя бы одно из этих условий не соблюдается, файл не будет загружен.
В Windows NT существует недокументированная возможность отключения выравнивания, основанная на том, что загрузку прикладных исполняемых файлов/динамических библиотек и системных драйверов обрабатывает один и тот же загрузчик.
Если SectionAlignment = FileAlignment, то последнее может принимать любое значение, представляющее собой степень двойки (например, 20h). Крис Касперский предлагает называть такие файлы «невыровненными».
К «невыровненным файлам» предъявляется следующее требование ― виртуальные и физические адреса всех секций обязаны совпадать, то есть страничный имидж должен полностью соответствовать своему дисковому образу.
Виртуальный размер секций может быть меньше их физического размера, но не более чем (SectionAlignment―1) байт (секция все равно будет выровнена в памяти). Если физический размер последней секции выходит за пределы загружаемого файла, операционная система выбросит «голубой экран смерти». Windows 9x не способна обрабатывать «невыровненные файлы». Для создания «невыровненных файлов» можно воспользоваться линкером от Microsoft, задав ему ключ /ALIGN:N, где N ― число представляющее собой степень двойки, но N не должно быть менее 16. (Но проверку того, что N<16 можно принудительно отключить при помощи «хирургического вмешательства» в тело линкера и EXE-файлы с выравниванием меньше 16 Windows XP благополучно загружает ).
SectionAlignment ― секция при загрузке PE-файла в память будет начинаться с адреса кратного данной величине.
Ограничения данного поля:
  1. Значение представляет собой степень двойки.
  2. SectionAlignment http://www.cyberforum.ru/cgi-bin/latex.cgi?\geq FileAlignment.
Пусть дано значение адреса x. Для получения выровненного значения адреса z можно использовать следующую формулу:z = (x + (y-1))&(~(y-1)) где y ― выравнивающий фактор.
FileAlignment ― эта величина соответствует смещению секций в файле.
Размер каждой секции кратен данной величине. Ограничения данного поля:
  1. Значение представляет собой степень двойки.
  2. Должно быть между 200h и 10000h.
  3. SectionAlignment http://www.cyberforum.ru/cgi-bin/latex.cgi?\geq FileAlignment.
[IMAGE_OPTIONAL_HEADER] MajorOperatingSystemVersion/MinorOperatingSystemVersion
Версия ОС, для которой данный файл предназначен. Совершенно никем не проверяемое поле. Может быть любым, но лучше чтобы не нулевое.
[IMAGE_OPTIONAL_HEADER] MajorImageVersion/MinorImageVersion
Это поле специально для того, чтобы программист создающий программу мог указать версию исполняемого образа. Может быть любым.
[IMAGE_OPTIONAL_HEADER] MajorSubsystemVersion/MinorSubsystemVersion
Поле содержит самую старую версию подсистемы, позволяющую запускать данный файл. Должно быть правильным.
[IMAGE_OPTIONAL_HEADER] reserved
Зарезервировано. Может быть любым.
[IMAGE_OPTIONAL_HEADER] SizeOfImage
Размер страничного имиджа, выровненный на величину Section Alignment. Размер страничного имиджа всегда равен виртуальному адресу последней секции плюс ее размер (выровненный, виртуальный). Если размер страничного образа вычислен неправильно, файл не загружается. Содержит общий размер всех частей отображения. Важно, что загрузчик проверяет значение этого поля по следующей формуле:
SizeOfImage = VirtualSize + VirtualAddress
14
Миниатюры
Сам себе Iczelion   Сам себе Iczelion   Сам себе Iczelion  

Сам себе Iczelion  
Mikl___
Заблокирован
Автор FAQ
03.01.2013, 17:11  [ТС] #7
[IMAGE_OPTIONAL_HEADER] SizeOfHeaders
Суммарный размер всех заголовков, сообщающий загрузчику, сколько байт читать от начала файла. Вычисляется по формуле SizeOfHeaders = DOS Stub + PE Header + Object TableКратно значению FileAlignment. Должно быть корректным. С этим полем связано два ограничения:
  1. SizeOfHeaders должен быть выбран так, чтобы загрузчик считал все, что ему необходимо прочитать,
  2. он не может превышать RVA первой секции (поскольку в противном случае какая-то часть секции оказалась бы спроецированной на область памяти, принадлежащей заголовку, а это недопустимо, ибо ни на какую страницу файла не могут отображаться более одного сектора одновременно).
Обычно SizeOfHeaders устанавливается на конец Section Table, однако это не самое лучшее решение. Совокупный размер всех заголовков при стандартной MS-DOS заглушке составляет порядка ~300h байт или меньше, в то время как физический адрес первой секции ― от 400h байт и выше. Отодвинуть секцию назад нельзя ― выравнивание не позволяет (см. «FileAlignment/SectionAlignment»). Если вынуть MS-DOS заглушку, можно ужать SizeOfHeaders до 200h байт, до начала первой секции. Если следовать рекомендациям от Microsoft, ~100h байт неизбежно теряется. Поэтому некоторые линкеры размешают здесь таблицу имен, содержащую перечень загружаемых DLL или что-либо подобное. Поэтому лучше всего подтянуть SizeOfHeaders к min(pFirstSection-->RawOffset, pFirstSection-->va).
[IMAGE_OPTIONAL_HEADER] CheckSum
Контрольная сумма образа файла. Проверяется только Windows NT при загрузке некоторых системных библиотек или ядра. Для обычных исполняемых файлов контрольная сумма не проверяется. Для всех системных DLL должна быть корректной. Чтобы получить контрольную сумму данного исполняемого файла надо вызвать функцию CheckSumMappedFile с соответствующими параметрами. Функция доступна из библиотеки imagehlp.dll.
[IMAGE_OPTIONAL_HEADER] Subsystem
Требуемая подсистема, которую операционная система должна предоставить файлу. Если подсистема CUI, то Windows создает консольное окно при старте программы. Может принимать следующие значения:
Кликните здесь для просмотра всего текста
значение название комментарий
0 IMAGE_SUBSYSTEM_UNKNOWN Неизвестная подсистема, файл не загружается.
1 IMAGE_SUBSYSTEM_NATIVE Подсистема не требуется, файл исполняется в «родном» окружении ядра и скорее всего представляет собой драйвер устройства. Обычным путем не загружается. Внимание: при загрузке драйверов Windows игнорирует поле подсистемы и оно может быть любым, поэтому если значение Subsystem не равно IMAGE_SUBSYSTEM_NATIVE, то это еще не значит, что данный файл не является драйвером.
2 IMAGE_SUBSYSTEM_WINDOWS_GUI Графическая win32 подсистема. Операционная система загружает файл нормальным образом.
3 IMAGE_SUBSYSTEM_WINDOWS_CUI Терминальная (она же консольная) win32 подсистема. То же самое, что и IMAGE_SUBSYSTEM_WINDOWS_GUI, но в этом случае файлу на халяву достается автоматически создаваемая консоль с готовыми дескрипторами ввода/вывода. Разница между консольными и графическими приложениями очень условна ― консольные приложения могут вызывать GUI32/USER32-функции, а графические приложения ― открывать одну или несколько консолей (например, в отладочных целях).
5 IMAGE_SUBSYSTEM_OS2_CUI Подсистема OS/2. Только для приложений OS/2 (одним из которых является всем известный HIEW32) и только для Windows NT. Windows 9x не может обрабатывать такие файлы.
7 IMAGE_SUBSYSTEM_POSIX_CUI Подсистема POSIX. Только для приложений UNIX и только для Windows NT
8 IMAGE_SUBSYSTEM_NATIVE_WINDOWS приложение ― драйвер Windows 9x
9 IMAGE_SUBSYSTEM_WINDOWS_CE_GUI Файл предназначен для исполнения в среде Windows CE. Ни Windows NT, ни Windows 9x не могут обрабатывать такие файлы
0Ah MAGE_SUBSYSTEM_EFI_APPLICATIONПодсистема EFI (Extensible Firmware Initiative)
0Bh IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER
0Ch IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER

[IMAGE_OPTIONAL_HEADER] DllCharacteristics
Определяет набор флагов, указывающих ― при каких условиях точка входа в DLL получает управление (а именно ― загрузка dll в адресное пространство процесса, создание/завершение нового потока и выгрузка dll из памяти). В спецификации на PE-формат эти поля помечены как зарезервированные и Windows игнорирует их значение, поэтому у большинства файлов оно равно нулю.
Согласно спецификации 6.0 от 1999 года, загрузчик должен поддерживать и другие флаги: 800h ― не биндить образ, 2000h ― загружать драйвер как WDM-драйвер;
8000h ― файл поддерживает работу под терминальным сервером. Экспериментальная проверка показала, что W2K игнорирует эти флаги.
Поле никогда не используется. Может быть любым.
[image_optional_header] SizeOfStackReserve/SizeOfStackCommit, SizeOfHeapReserve/SizeOfHeapCommit
SizeOfStackReserve ― Объем виртуальной памяти, резервируемой под начальный стек потока. Выделяется число байт указанное в следующем поле.
SizeOfStackCommit ― Объем виртуальной памяти, выделяемой под начальный стек потока.
SizeOfHeapReserve ― Объем виртуальной памяти, резервируемой под начальный хип программы.
SizeOfHeapCommit ― Объем виртуальной памяти, выделяемой под начальный хип программы.
Если SizeOfCommit > SizeOfReverse, файл не загружается. Ноль обозначает значение по умолчанию.
[IMAGE_OPTIONAL_HEADER] LoaderFlags
Не используемое поле. Может быть любым.
[IMAGE_OPTIONAL_HEADER] NumberOfRvaAndSizes
Количество элементов (не байт) в массиве DATA_DIRECTORY. Во всех относительно новых линкерах устанавливается в 10h. Даже константа IMAGE_NUMBEROF_DIRECTORY_ENTRIES в WINNT.H определена как 10h. Так что размер
опционального заголовка, скорее всего, будет 0E0h байт. (а мы сделаем 70h).
Из-за грубых ошибок в системном загрузчике, компоновщики от Borland и Microsoft всегда выставляют полный размер директории, равный 10h, даже если реально его не используют. Например, Windows 9x не проверяет, что
NumberOfRvaAndSizes http://www.cyberforum.ru/cgi-bin/latex.cgi?\geq RELOCATION и/или RESOURCE и если подсунуть ей запрос
к одной из этих секций, а таких директорий нет ― кранты. Windows NT не проверяет (при загрузке dll) «достаточности» TLS_DIRECTORY и если этот TLS-механизм активирован, а TLS-директории нет ― опять кранты.
По спецификации DATA_DIRECTORY располагается в самом конце опционального заголовка и непосредственно за его концом начинается таблица секций. Таким образом, указатель на таблицу секций может быть получен либо так:
Кликните здесь для просмотра всего текста
C
1
((BYTE*) ((*((WORD*)(p + 0x14 /* size of optional header */))) + 0x18 /* size of image header */ + p)),
либо так:
C
1
((BYTE*) ( (*((DWORD*)(p + 0x74 /* NumRVAandSize */))) * 8 + 0x78 /* begin DATA_DIRECTOTY */ + p)).
Системный загрузчик использует первый способ и допускает, что за между DATA_DIRECTORY и SECTION_TABLE может быть расположено некоторое количество «бесхозных» байт. Некоторые дизассемблеры и упаковщики считают иначе и ищут SECTION_TABLE непосредственно за концом DATA_DIRECTORY. В WINNT.H недвусмысленно говорится, что:
Кликните здесь для просмотра всего текста
C
1
2
3
#define IMAGE_FIRST_SECTION( ntheader ) ((PIMAGE_SECTION_HEADER)       \
        ((ULONG_PTR)ntheader + FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) +  \
        ((PIMAGE_NT_HEADERS)(ntheader))-->FileHeader.SizeOfOptionalHeader))

IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]
Каждый каталог данных является структурой IMAGE_DATA_DIRECTORY. Несмотря на то, что все каталоги данных одинаковы, каждый конкретный тип каталога данных уникален. Структура типа IMAGE_DATA_DIRECTORY определена следующим образом:
Кликните здесь для просмотра всего текста
C
1
2
3
4
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress; //RVA директории
    DWORD   Size;//Размер директории
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
Каждый элемент в массиве каталогов данных содержит свой размер и относительный виртуальный адрес каталога. Чтобы найти местоположение некоторого каталога, Вы должны определить относительный адрес из массива каталогов данных в опциональном заголовке. Далее используйте виртуальный адрес для определения, в каком сегменте находится нужный каталог. После определения сегмента, содержащего нужный каталог данных, используйте заголовок этого сегмента для определения смещения в файле, указывающего местоположение нужного каталога данных.
Каждый элемент массива указывает на какую-либо структуру, например на таблицу импорта. То есть каждый элемент это информация о директории, каждая из которых несет собой определенную смысловую нагрузку. Определенный индекс в массиве соответствует определенной директории. Директория может быть секцией, а может быть частью секции. Если нужно найти, например таблицу экспорта, то обращаемся к элементу 0 этого массива. Вот полный перечень всех индексов:
Кликните здесь для просмотра всего текста
IMAGE_DIRECTORY_ENTRY_EXPORT 0 Каталог экспортируемых объектов
IMAGE_DIRECTORY_ENTRY_IMPORT 1 Каталог импортируемых объектов
IMAGE_DIRECTORY_ENTRY_RESOURCE 2 Каталог ресурсов
IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 Каталог исключений
IMAGE_DIRECTORY_ENTRY_SECURITY 4 Каталог безопасности
IMAGE_DIRECTORY_ENTRY_BASERELOC 5 Таблица базовых поправок (переадресации)
IMAGE_DIRECTORY_ENTRY_DEBUG 6 Отладочный каталог
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 Данные специфичные для архитектуры
IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 RVA глобальных указателей
IMAGE_DIRECTORY_ENTRY_TLS 9 Каталог TLS (Thread local storage ― локальная память потоков)
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 Каталог конфигурации загрузки
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 Директория Bound-импорта
IMAGE_DIRECTORY_ENTRY_IAT 12 Таблица импортированных адресов (IAT)
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 Дескриптор delay-импорта
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 COM Runtime дескриптор
Структура IMAGE_DATA_DIRECTORY содержит в себе RVA-директории. Если файл проецирован не как SEC_IMAGE, то сразу найти смещение в файле данной директории не удастся. Для этой операции используйте функцию RVAtoOffset.
Кликните здесь для просмотра всего текста
0 IMAGE_DIRECTORY_ENTRY_EXPORT Указатель на таблицу экспортируемых функций и данных (далее по тексту ― просто функций). Встречается преимущественно в динамических библиотеках и драйверах, однако заниматься экспортом может и обычный исполняемый файл. Использует RVA- и VA-адресацию (подробнее см. «Экспорт»).
1 IMAGE_DIRECTORY_ENTRY_IMPORT Указатель на таблицу импортируемых функций, используемую для связи файла с внешним миром и активируемую системным загрузчиком, когда все остальные механизмы импорта недоступны. Использует RVA- и VA-адреса (подробнее см. «Импорт»).
2 IMAGE_DIRECTORY_ENTRY_RESOURCE Указатель на таблицу ресурсов, хранящую строки, пиктограммы, курсоры, диалоги и пр. Таблица ресурсов организована в виде трехуровневого двоичного дерева, использующего RVA-адресацию, то есть не чувствительного к смещению «своей» секции (это, как правило, секция «.rsrc») внутри файла.
3 IMAGE_DIRECTORY_ENTRY_EXCEPTION Указывает на exception directory (директорию исключений), обычно размещаемую в секции «.pdata» (хотя это и необязательно). Используется только на следующих архитектурах: MIPS, Alpha32/64, ARM, PowerPC, SH3, SH, WindowsCE. К микропроцессорам семейства Intel это не относится и i386-загрузчик игнорирует это поле, поэтому оно может принимать любое значение.
4 IMAGE_DIRECTORY_ENTRY_SECURITY Указывает на Certificate Table (таблицу сертификатов), располагающуюся строго в секции «.debug» и адресуемой не по RVA-адресам, а по физическим смещениям внутри файла (так происходит потому, что таблица сертификатов не грузится в память и обитает исключительно на диске).
5 IMAGE_DIRECTORY_ENTRY_BASERELOC Он же fixup. Использует RVA-адреса. (см. «Перемещаемые элементы»).
6 IMAGE_DIRECTORY_ENTRY_DEBUG Отладочная информация, используемая дизассемблерами и дебаггерами. Использует RVA- и RAW OFFSET-адресацию. Системный загрузчик ее игнорирует.
7 IMAGE_DIRECTORY_ENTRY_ARCHITECTURE Оно же «description». На i386-платформе предназначен для хранения информации о копирайтах (на это, в частности, указывает определение IMAGE_DIRECTORY_ENTRY_COPYRIGHT, данное в WINNT.H), за формирование которых отвечает ключ -D, переданный линкеру ilinlk32.exe, при этом в IMAGE_DIRECTORY_ENTRY_ARCHITECTURE помещается RVA-указатель на строку комментариев, по умолчанию располагающуюся в секции «.text». Компоновщик ms link при некоторых до конца не выясненных обстоятельствах помещает в это поле информацию об архитектуре, однако системный загрузчик ее никогда не использует.
8 IMAGE_DIRECTORY_ENTRY_GLOBALPTR Указатель на таблицу регистров глобальных указателей. Используется только на процессорах ALPHA и PowerPC. На i386-платформе это поле лишено смысла и загрузчик его игнорирует.
9 IMAGE_DIRECTORY_ENTRY_TLS Хранилище статической локальной памяти потока (Thread Local Storage). TLS-механизм обеспечивает «прозрачную» работу с глобальными переменными в многопоточных средах без риска, что переменная в самый неподходящий момент будет модифицирована другим потоком. Сюда попадают переменные, объявленные как __declspec(thread). По причине большой причудливости и крайней тяжеловесности реализации используется крайне редко. Windows NT и Windows 9x обрабатывают это поле неодинаково. Хранилище обычно размещается в секции «.tls», хотя это и необязательно. Использует RVA- и VA-адреса.
10 IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG Содержит информацию о конфигурации глобальных флагов, необходимых для нормальной работы программы, имеет смысл только в Windows NT и производных от нее системах. Это поле практически никем не используется, но если возникнет желание узнать о нем больше ― см. прототип структуры IMAGE_LOAD_CONFIG_DIRECTORY32 в WINNT.h, а также ее описание в Platform SDK. За описанием самих флагов обращайтесь к утилите gflags.exe, вохоящей в состав Resource Kit и NTDDK. Информация о конфигурации использует VA-адресацию.
11 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT Указатель на таблицу диапазонного импорта, имеющей приоритет над IMAGE_DIRECTORY_ENTRY_IMPORT и обрабатываемой загрузчиком в первую очередь (зачастую, до IMAGE_DIRECTORY_ENTRY_IMPORT дело вообще не доходит). По устоявшейся традиции таблица диапазонного импорта размещается в PE-заголовке, хотя это и не обязательно и некоторые линкеры ведут себя иначе. Используется RVA- и RRAW OFFSET-адресация (подробнее см. «Импорт»).
12 IMAGE_DIRECTORY_ENTRY_IAT Указатель на IAT (подчиненная структура таблицы импорта). Используется загрузчиком Windows XP, остальные операционные системы это поле, по-видимому, игнорируют (подробнее см. «Импорт»).
13 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT Указатель на таблицу отложенного импорта, использующую RVA/VA-адресацию, но фактически остающуюся нестандартизованной и отданной на откуп воле конкретных реализаторов (подробнее см. «Импорт»).
14 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR Если не равно нулю, то файл представляет собой .NET-приложение, состоящее из байт-кода.

Таблица секций
Спецификация PE-файла состоит из заголовков, описанных ранее, и общих объектов, называемых секциями. Секции содержат собственно содержимое файла, включая код, данные, ресурсы и прочую информацию о PE-файле. Каждая секция имеет заголовок и тело секции (непосредственно данные). Заголовки секций описаны ниже, однако тела секций не имеют жестко заданной структуры. Они организованы почти всегда так, как того захотел конкретный линкер, поскольку заголовки содержат достаточно информации для дальнейшей обработки данных.
Сразу после PE-заголовка (который состоит из постоянной части длиной 18h байтов и переменной части длиной SizeOfOptionalHeader байтов) размещается таблица описания всех секций. Она содержит NumberOfSections записей следующей структуры:
Кликните здесь для просмотра всего текста
Поле Смещ. Длина Назначение
Name +00h 8 Символьное имя секции
VirtualSize +0Сh 4 Реальное количество информации в секции
VirtualAddress +10h 4 Смещение секции в памяти от ImageBase
SizeOfRawData +14h 4 Размер, зарезервированный для секции на диске
PointerToRawData +18h 4 Смещение секции от начала файла
Characteristics +20h 4 Флаги свойств секции
Четкого определения термина «секция» не существует. Упрощенно говоря, секция ― это непрерывная область памяти внутри страничного имиджа со своими атрибутами, не зависящими от атрибутов остальных секций. Представление секции в памяти не обязательно должно совпадать с ее дисковым образом, который в принципе может вообще отсутствовать (секциям неинициализированных данных нечего делать на диске и потому они представлены исключительно в памяти).
Каждая секция управляется «своей» записью в одноименной структуре данных, носящей имя «таблицы секций». Таблица секций представляет собой массив структур IMAGE_SECTION_HEADER, количество задается полем NumberOfSection.
Порядок секций может быть любым, но системный загрузчик оптимизирован под следующую последовательность: сначала идет кодовая секция, за ней следует одна или несколько секции инициализированных данных и замыкает строк секция неинициализированных данных.
Таблица секций ― это база данных, для всех секций используемых в PE-файле. В PE-файле теоретически может быть сколько угодно секций. Все они могут иметь одинаковые атрибуты и даже одинаковые имена, кроме секции ресурсов. Но обычно секции делят либо по их логическому предназначению, либо по атрибутам. Загрузчик ориентируется на массив DataDirectory в опциональном заголовке, для того чтобы найти нужные данные. Это сделано в целях оптимизации, чтобы не сравнивать строки, а просто перейти сразу же к нужной директории с помощью соответствующих индексов. Программист может создавать собственные секции и давать им произвольные имена. В приложениях Windows NT могут использоваться следующие стандартные секции:
Кликните здесь для просмотра всего текста
вариант от Microsoft вариант от Borland комментарий атрибуты секции значение атрибута
.text CODE код программы IMAGE_SCN_CNT_CODE +IMAGE_SCN_MEM_EXECUTE +IMAGE_SCN_MEM_READ 20000000h+40000000h+20h = 60000020h
.bss .CRT неинициализированных данных IMAGE_SCN_CNT_UNINITIALIZED_DATA+ IMAGE_SCN_MEM_READ +IMAGE_SCN_MEM_WRITE 80000000h+40000000h+80h = 0A00000080h
.rdata данные только для чтения IMAGE_SCN_CNT_INITIALIZED_DATA+ IMAGE_SCN_MEM_READ40000000h+40h= 40000040h
.edata экспорт  
.xdata данные об исключениях  
.pdata    
.data DATA глобальные переменныеIMAGE_SCN_CNT_INITIALIZED_DATA+ IMAGE_SCN_MEM_READ +IMAGE_SCN_MEM_WRITE80000000h+40000000h+ 40h = 0A0000040h
.rsrc ресурсы  
.idata импорт  
.tls локальная память потока  
.reloc таблица перемещений IMAGE_SCN_CNT_INITIALIZED_DATA+ IMAGE_SCN_MEM_READ +IMAGE_SCN_MEM_DISCARDABLE 40000000h+2000000h+ 40h = 42000040h
.debug отладочная информация  
.arch информация об архитектуре Alpha IMAGE_SCN_MEM_READ +IMAGE_SCN_CNT_INITIALIZED_DATA + IMAGE_SCN_ALIGN_8BYTES +IMAGE_SCN_MEM_DISCARDABLE 40000000h+2000000h+ 400000h + 40h = 42400040h
Такие секции создают линкеры, опираясь на спецификацию Microsoft. Таблица секций это массив элементов типа IMAGE_SECTION_HEADER.
Заголовки секций
Заголовки секций размещаются последовательно сразу же за опциональным заголовком PE файла. Каждый заголовок секции имеет длину 40 байт и расположены они без выравнивания. Заголовок секции определяется следующей структурой:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
IMAGE_SECTION_HEADER STRUCT
    Name1 db IMAGE_SIZEOF_SHORT_NAME dup(?)
    union Misc
        PhysicalAddress  dd ?
        VirtualSize  dd ?
    ends
    VirtualAddress   dd ?
    SizeOfRawData    dd ?
    PointerToRawData     dd ?
    PointerToRelocations dd ?
    PointerToLinenumbers dd ?
    NumberOfRelocations  dw ?
    NumberOfLinenumbers  dw ?
    Characteristics  dd ?
IMAGE_SECTION_HEADER ENDS
Структура IMAGE_SECTION_HEADER состоит из следующих полей:
Поля заголовка секции
  • Name. Каждый заголовок секции имеет свое имя, поле Name, длиной до 8 символов, в котором первым символом может быть точка «.»
  • Это поле ― объединение PhysicalAddress и VirtualSize, в настоящий момент не используется.
  • VirtualAddress. Это поле содержит виртуальный адрес в адресном пространстве процесса, в который загружается секция. Действительный адрес создается из значения этого поля, добавленного к виртуальному
    адресу ImageBase в структуре опционального заголовка. Однако помните, что если файл является DLL, нет гарантии того, что DLL будет загружена по адресу, указанному в поле ImageBase. Так что после загрузки файла, действительное значение ImageBase нужно проверить программно с помощью функции
    GetModuleHandle.
  • SizeOfRawData. Это поле содержит размер тела секции, связанный с полем File Alignment. Действительный размер тела секции будет числом, меньшим или равным ближайшему числу, кратному полю File Alignment. После загрузки файла в адресное пространство процесса, размер тела секции становится меньшим или равным ближайшему числу, кратному полю Section Alignment.
  • PointerToRawData. Это смещение на собственно тело секции в файле.
  • PointerToRelocations, PointerToLinenumbers, NumberOfRelocations, NumberOfLinenumbers.
    Ни одно из этих полей не используется.
  • Characteristics. Определяет характеристики сегмента. Значения этого поля могут быть найдены в файле заголовков WINNT.H.
Опишем по порядку эти поля:
BYTE Name[8]
Название секции. Поле Name представляет собой 8-байтовый массив с ASCII-именем секции внутри (именно именем, а не указателем на имя!). Если длина имени меньше восьми байт, оставшийся хвост дополняется нулями, если же имя занимает весь массив целиком, завершающий нуль в его конце не ставится (некоторые дизассемблеры не учитывают этого обстоятельства и захватывают примыкающий к массиву мусор).
Само по себе имя секции не несет никакого метафизического смысла и было введено в эксплуатацию исключительно из эстетических соображений. Системный загрузчик его игнорирует. Библиотека oleaut32.dll, входящая в состав Windows, опознает секцию ресурсов по ее имени, а не по записи в DATA_DIRECTORY. В исходных текстах упаковщика UPX присутствует следующий комментарий: «...after some windoze debugging I found that the name of the sections DOES matter .rsrc is used by oleaut32.dll (TYPELIBS) and because of this lame dll, the resource stuff must be the first in the 3rd section ― the author of this dll seems to be too idiot to use the data directories... M$ suxx 4 ever! ...even worse: exploder.exe in NiceTry also depends on this to locate version info». Дизассемблирование подтверждает, что библиотека oleaut32.dll действительно содержит внутри себя текстовую строку «.rsrc» и активно ее использует.
DWORD VirtualSize
Для EXE-файлов содержит виртуальный размер секции. То есть это размер, выровненный на Section Alignment. Если это значение равно нулю, то загрузчик использует значение SizeOfRawData выровненное на Section Alignment. Если это значение не выровнено, так как загрузчик может выровнять его сам в случае необходимости. Если это значение больше SizeOfRawData, то в памяти секция выравнивается нулями. Если это значение меньше SizeOfRawData, то: здесь начинаются расхождения реализации загрузчиков, так что на это лучше не полагаться. Для объектных файлов это поле указывает физический адрес секции.
Поля VirtualSize и SizeOfRawData содержат виртуальную и физическую длину секции, соответственно. Если виртуальный размер больше физического, то при загрузке секции в память ее хвост заполняется нулями, при этом наличие атрибута инициализированных/неинициализированных данных совершенно необязательно. Если физический размер больше виртуального, то... единственное, что можно сказать с уверенностью, такой файл будет нормально загружен в память.
Нулевой виртуальный размер предписывает загрузчику отталкиваться от физического размера секции, предварительно округлив его на величину Section Alignment и заполнив хвост нулями. Все промежуточные состояния неопределенны ― загрузчик может считать:
  1. ровно Virtual Size байт;
  2. Virtual Size байт округленное вверх на File Alignment;
  3. Virtual Size байт округленное вверх на Phys Sector Size.
Физический размер должен быть выровнен на величину File Alignment, выравнивать виртуальный размер необязательно (загрузчик выравнивает его автоматически). Если физический размер меньше или равен виртуальному, то и его выравнивать не обязательно, поскольку начало следующей секции в файле должно быть выровнено на величину File Alignment.
Виртуальный адрес следующей секции обязательно должен быть равен виртуальному адресу предыдущей секции плюс ее размер, выровненный на величину Section Alignment. Секции не могут ни перекрываться, ни образовывать виртуальные дыры. На физические адреса секций таких ограничений не наложено. Если Section Alignment < 1000h, а физический размер секции выходит за пределы файла, W2K SP3 (вероятно, и все остальные представители линейки NT) выбрасывают синий экран.
DWORD VirtualAddress
Это поле содержит адрес, куда загрузчик должен отобразить секцию. Поле является RVA.
Поля VirtualAddress и PointerToRawData содержат RVA-адрес начала секции в памяти и ее смещение относительно начала файла, соответственно.
Виртуальный и физический адреса должны быть выровнены на величину Section Alignment/File Alignment, прописанную в опциональном заголовке, причем виртуальный адрес первой секции должен быть равен ALIGN_UP(SizeOfHeaders, SectionAlignment), в противном случае файл не загрузится. Физический адрес секции может быть любым, достаточно только, чтобы он был выровнен на величину File Alignment.
DWORD SizeOfRawData
Это поле содержит размер секции, выровненный на ближайшую верхнюю границу размера файла. Обычно SizeOfRawData ― это «круглое» число, кратное значению FileAlignment (часто это 512 байтов), а значение поля VirtualSize ― число «не круглое», характеризующее реальную длину секции. Значит, между реальным концом области, содержащей «полезные» байты содержимого секции, и концом фрагмента дисковой памяти, зарезервированным за этой секцией, обычно присутствует неиспользуемый «зазор», величина которого может варьироваться от 1 до 511 байтов.
DWORD PointerToRawData
Это значение, есть файловое смещение, откуда брать исходные данные для секции при отображении.
DWORD PointerToRelocations
Поля PointerToRelocations/NumberOfRelocations (указатель на таблицу перемещаемых элементов и количество элементов в этой таблице, соответственно) имеют отношение только к объектным файлам, а исполняемые файлы и динамические библиотеки управляют своими перемещаемыми элементами через одноименную запись в DATA_DIRECTORY, поэтому эти поля могут содержать любые значения.
DWORD PointerToLinenumbers
Файловое смещение таблицы номеров строк. Поля PointerToLinenumbers/NumberOfLinenumbers (указатели на таблицу номеров строк и количество элементов в этой таблице, соответственно) ранее использовались для хранения отладочной информации, связывающей номера строк исходной программы с адресами откомпилированного файла. В настоящее время используется только в объектных файлах, а в исполняемых файлах отладочная информация хранится совсем в другом месте и в другом формате.
WORD NumberOfRelocations
Количество перемещений в таблице базовых поправок для данной секции. Так как значение указателя на таблицу базовых поправок храниться в массиве DataDirectory, то это поле тоже может быть любым.
WORD NumberOfLinenumbers
Количество номеров строк для данной секции. Так как поле PointerToLinenumbers не используется, то может принимать любые значения.
DWORD Characteristics
Поле Characteristics определяет атрибуты доступа к секции и особенности ее загрузки. Имеются три атрибута определяющих содержимое секции как код, инициализированные и неинициализированные данные (IMAGE_SCN_CNT_CODE, IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_CNT_UNINITIALIZED_DATA, соответственно). Системный загрузчик игнорирует их значение. Теоретически секция неинициализированных данных при отсутствии прочих атрибутов не должна грузиться с диска, но... ведь грузится!
Иногда кодовую секцию определяют по наличию атрибута IMAGE_SCN_CNT_CODE.
Но иногда этого атрибута может не оказаться ни у одной из секции, либо же он будет присвоен секции данных.
Три других атрибута описывает права доступа ко всем страницам секции, назначаемым системным загрузчиков по умолчанию (будучи загруженным, файл может свободно манипулировать ими, вызывая API-функцию VirtualProtectEx). В настоящее время определено три атрибута: исполнения, чтения и записи (IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE). На платформе Intel атрибуты чтения/исполнения полностью эквивалентны и соответствуют аппаратному атрибуту доступности (accessible) страницы. Атрибут записи обрабатывается вполне естественным образом. Следовательно, отличить секцию кода от секции данных в общем случае невозможно и приходится считать секцией кода ту, в которую указывает точка входа.
Атрибут IMAGE_SCN_MEM_DISCARDABLE (после загрузки файла секция может быть уничтожена в памяти) обычно присваивается секциям, содержащим вспомогательные структуры данных ― например такие, как таблица перемещаемых элементов, необходимые лишь на этапе загрузки файла и впоследствии никем не используемые. Операционная система в любой момент может выгрузить занятые ими страницы.
Атрибут IMAGE_SCN_MEM_SHARED ― секция является совместно используемой. Флаги IMAGE_SCN_MEM_EXECUTE и IMAGE_SCN_MEM_READ эквивалентны.
Остальные атрибуты либо не интересны, либо имеют отношение только к объектным coff-файлам (не PE) и потому здесь не рассматриваются. Это, в частности, относится к атрибутам из семейства IMAGE_SCN_ALIGN_хBYTES, индивидуально настраивающим кратность выравнивания каждой секции. Для объектных файлов это, быть может, и так, но системный загрузчик эти атрибуты в упор игнорирует.

Это поле содержит атрибуты секции. Атрибуты секции указывают на права доступа к ней, а также на некоторые особенности влияния на нее загрузчика. Флаги секций могут преобразовываться загрузчиком в атрибуты страниц и сегментов. Это поле всегда не равно нулю (а у меня равно ― и тем не менее всё работает ). Часть флагов используются только в объектных файлах, либо вообще не используются.
Кликните здесь для просмотра всего текста
название значение комментарий
? 00000001h?
00000002h 
00000004h используется для кода с 16-битными смещениями
00000008h?
00000010h 
IMAGE_SCN_CNT_CODE 00000020h Секция содержит код
IMAGE_SCN_CNT_INITIALIZED_DATA 00000040h Секция содержит инициализированные данные
IMAGE_SCN_CNT_UNINITIALIZED_DATA 00000080h Секция содержит неинициализированные данные.
IMAGE_SCN_LNK_OTHER 00000100h Зарезервировано
IMAGE_SCN_LNK_INFO 00000200h Секция содержит комментарии или другой тип информации
? 00000400h оверлейная секция
IMAGE_SCN_LNK_REMOVE 00000800h Содержимое секции не станет отображаться в адресное пространство процесса. Этот флаг имеет смысл только в OBJ-файлах и управляет включением секции в скомпонованный EXE- или DLL-файл
IMAGE_SCN_LNK_COMDAT 00001000h Секция содержит общие данные
? 00002000h?
00004000h  
IMAGE_SCN_MEM_FARDATA 00008000h якобы зарезервирован, но в WinNT.h он равен IMAGE_SCN_GPREL, у которого есть неясное значение: Section content can be accessed relative to GP.
? 00010000h 
IMAGE_SCN_MEM_PURGEABLE 00020000h  
IMAGE_SCN_MEM_16BIT 00020000h  
IMAGE_SCN_MEM_LOCKED 00040000h  
IMAGE_SCN_MEM_PRELOAD 00080000h  
IMAGE_SCN_ALIGN_1BYTES 00100000hкратность выравнивания каждой секции
IMAGE_SCN_ALIGN_2BYTES 00200000h  
IMAGE_SCN_ALIGN_4BYTES 00300000h  
IMAGE_SCN_ALIGN_8BYTES 00400000h  
IMAGE_SCN_ALIGN_16BYTES 00500000h Выравнивание по умолчанию, если не указаны другие
IMAGE_SCN_ALIGN_32BYTES 00600000h кратность выравнивания каждой секции
IMAGE_SCN_ALIGN_64BYTES 00700000h  
? 00800000h ?
IMAGE_SCN_LNK_NRELOC_OVFL 01000000h Секция содержит информацию для переадресации. Если установлен этот флаг, и поле NumberOfRelocations=FFFF, то число элементов в таблице перемещений (на которую указывает PointerToRelocations) превышает FFFF; фактическое число в этом случае содержится в поле VirtualAddress первого элемента таблицы. Поле имеет смысл только для OBJ.
IMAGE_SCN_MEM_DISCARDABLE 02000000h Секция может быть выгружена. Эта секция отбрасывается, когда программа уже загружена. Вообще-то, в ранних версиях Windows такой атрибут секции означал, что при недостатке памяти она становится кандидатом на временную выгрузку из памяти. В MSDN встречаются такие рекомендации для создателей драверов: сделайте секцию ресурсов Discardable (а ведь она может еще понадобиться!). Luevelsmeyer считает, что после запуска процесса данные такой секции больше не нужны (а значит, после запуска ее можно вообще удалить из памяти). Кстати, в своей работе Luevelsmeyer недоумевает по поводу флага IMAGE_SCN_MEM_PURGEABLE (так как трудно понять, в чем разница между этими двумя флагами) - так вот, в документации MSDN этот флаг объявлен зарезервированным для будущего использования.
IMAGE_SCN_MEM_NOT_CACHED 04000000h Секция не может быть кэширована. Информация из этой секции не должна помещаться в кэш процессора.
IMAGE_SCN_MEM_NOT_PAGED 08000000h Секция не может быть поделена на страницы. Секция не должна подключаться к механизму страничной адресации и виртуальной памяти. Иногда этот флаг интерпретируют по-другому: "Section cannot be paged out", что можно понять как "секция не должна выгружаться из памяти в файл подкачки". Смотри также IMAGE_SCN_MEM_DISCARDABLE.
IMAGE_SCN_MEM_SHARED 10000000h Секция является общедоступной или разделяемой. Секцию разделяют все процессы, загрузившие данный файл. Выделенная секции область памяти доступна для совместного использования (Section can be shared in memory). Все загруженные в память экземпляры данного модуля, таким образом, читают данные этой секции из одной и той же физической области памяти. Причем инициализация содержимого секции происходит лишь единожды, при загрузке первого экземпляра.
  возможные виды доступа
IMAGE_SCN_MEM_EXECUTE 20000000h Секция является исполняемой.
IMAGE_SCN_MEM_READ 40000000h Данные секции можно читать.
IMAGE_SCN_MEM_WRITE 80000000h В секцию можно записывать данные.
Флаги могут быть использованы одновременно, если применять к ним побитовою операцию «или». Например, нам нужно чтобы в секцию можно было записывать, читать из нее, а также для пущей надежности указывает, что секция содержит код. Таким образом итоговое значение поля Characteristics будет выглядеть следующим образом:
80000000h + 40000000h + 20h = 0A0000020h
Экспорт
Кликните здесь для просмотра всего текста

Рисунок 3. Структура IMAGE_EXPORT_DIRECTORY
Таблица экспорта представляет собой сложную иерархическую структуру, каждый из компонентов которой может быть расположен в любом месте страничного имиджа, хотя по спецификации она должна быть сосредоточена в одной области. Когда-то таблице экспорта выделялась своя персональная секция «.edata», но теперь этого правила практически никто не придерживается, поэтому говорить о секции экспорта не совсем корректно.
На вершине иерархии находится структура IMAGE_EXPORT_DIRECTORY, также известная под именем export directory table, содержащая указатели на три подчиненные структуры: таблицу экспортируемых имен (Name Pointer), таблицу экспортируемых ординалов (Ordinal Table) и таблицу экспортируемых адресов (Export Address Table). Поле Name RVA указывает на строку с именем динамической библиотеки, которое судя по всему игнорируется и может принимать любые значения.
Местоположение каталогов данных
Каталоги данных расположены в телах соответствующих секций. Обычно каталог данных является первой структурой в теле секции, однако это не является обязательным. По этой причине, Вы должны использовать информацию как из заголовка секции, так и из опционального заголовка для определения местоположения определенного каталога данных.
Предопределенные секции
Файлы PE-формата имеют одиннадцать предопределенных секций, являющихся общими для приложений Windows NT, но любое приложение может определять собственные секции для кода и данных.
Предопределенная секция «.debug» может быть также перемещена из исполнимого файла в отдельный отладочный файл. В таком случае, специальный отладочный заголовок используется для манипуляций с отладочным файлом, а в исполнимом файле устанавливается флаг, означающий, что отладочная информация была удалена.
Приложения под Windows NT обычно имеют 9 предопределенных секций, называемых «.text», «.bss», «.rdata», «.data»,
«.rsrc», «.edata», «.idata», «.pdata» и «.debug». Некоторым приложениям не нужны все из этих секции, в то время как другие могут определять дополнително к этим и свои собственные секции для удовлетворения некоторых специфических требований. Это похоже на сегменты кода и данных в MS-DOS и Windows версии 3.1. На самом деле, способ, которым приложение определяет уникальные секции, используя стандартные директивы компилятора для именования сегментов данных и кода или используя опцию именования сегментов компиллятора -NT - является тем же самым, с помощью которого приложения определяли уникальные сегменты кода и данных в версии 3.1. Ниже описываются несколько наиболее интересных секций, общих для типичных PE файлов Windows NT
Секция исполнимого кода, «.text»
Единственная разница между Windows версии 3.1 and Windows NT ― то, что в Windows NT все секции кода (как они назывались в Windows весрии 3.1 ) комбинируются в одну секцию кода, называемый «.text». Так как Windows NT использует страничную организацию виртуальной памяти, нет преимуществ в разделении кода на несколько ограниченных секций кода. Более того, наличие только одной секции кода облегчает жизнь и операционной системе, и разработчику приложений.
Секция «.text» также содержит точку входа, упоминавшуюся ранее. Также непосредственно перед точкой входа расположена таблица импортируемых адресов (IAT). (Наличие IAT в секции кода имеет смысл, потому что эта таблица представляет собой лишь серию инструкций jmp, адреса которых фиксированы и известны). Когда исполнимый файл Windows NT загружается в адресное пространство процесса, IAT заполняется действительными адресами импортируемых функций. Чтобы найти IAT в загружаемом файле, загрузчик просто находит точку входа и основывается на факте, что IAT расположена непосредственно перед точкой входа.
Поскольку все элементы в этой таблице одинакового размера, не составляет труда пройтись по ее элементам, чтобы найти ее начало.
Секции данных, «.bss», «.rdata», «.data»
Секция «.bss» представляет неинициализированные данные приложения, включая все переменные, декларированные как статические во всех функциях или модулях.
Секция «.rdata» представляет данные только для чтения, такие, как строки, константы и информацию отладочного каталога.
Все другие переменные (за исключением автоматических, которые создаются на стеке) хранятся в секции «.data». В основном, это глобальные переменные приложения и отдельных модулей.
Секция ресурсов, «.rsrc»
Секция «.rsrc» содержит информацию о ресурсах приложения. Она начинается с каталога ресурсов, как и большинство других секций, но этот каталог структурирован в виде дерева ресурсов. Структура IMAGE_RESOURCE_DIRECTORY,
приведенная ниже, формирует корень и ветви дерева:
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
6
7
8
typedef struct _IMAGE_RESOURCE_DIRECTORY {
    ULONG   Characteristics;
    ULONG   TimeDateStamp;
    WORD  MajorVersion;
    WORD  MinorVersion;
    WORD  NumberOfNamedEntries;
    WORD  NumberOfIdEntries;
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
Посмотрев на структуру каталога, Вы не найдете никакого указателя на следующий узел. Вместо этого, есть два поля, NumberOfNamedEntries и NumberOfIdEntries, используемые для указания количества элементов, содержащихся в каталоге. Под словом содержащихся подразумевается, что элементы каталога расположены непосредственно после самого каталога в теле секции. Именованные элементы расположены первыми и упорядочены в алфавитном порядке, далее следуют элементы, идентифицируемые целым числом, упорядоченные по своему идентификатору.
Элемент каталога состоит из двух полей, как описывается нижеприведенной структурой IMAGE_RESOURCE_DIRECTORY_ENTRY:
Кликните здесь для просмотра всего текста
C
1
2
3
4
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
    ULONG   Name;
    ULONG   OffsetToData;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
Эти два поля используются для различных вещей, в зависимости от уровня дерева. Поле Name используется для идентификации типа ресурса, имени ресурса либо языка ресурса. Поле OffsetToData всегда используется для указания на потомка, либо в ветви дерева, либо в конечном узле.
Конечные узлы ― это низшие узлы в дереве ресурсов. Они определяют размер и местоположение непосредственно данных ресурса. Каждый конечный узел представляет собой структуру IMAGE_RESOURCE_DATA_ENTRY:
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
6
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
    ULONG   OffsetToData;
    ULONG   Size;
    ULONG   CodePage;
    ULONG   Reserved;
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
Два поля OffsetToData и Size содержат местоположение и размер непосредственно данных ресурса. Так как эта информация используется по большей частью функциями после загрузки приложения, удобнее было сделать поле OffsetToData относительным виртуальным адресом. Это именно такой случай.
Достаточно интересно, что все другие смещения, такие, как указатели из элемента каталога на другие каталоги, являются смещениями относительно адреса корневого элемента.
Основные типы ресурсов определены в файле заголовков WINUSER.H и приводятся ниже:
Кликните здесь для просмотра всего текста
Предопределенные типы ресурсов значенияMAKEINTRESOURCE
RT_CURSOR 1
RT_BITMAP 2
RT_ICON 3
RT_MENU 4
RT_DIALOG 5
RT_STRING 6
RT_FONTDIR 7
RT_FONT 8
RT_ACCELERATOR 9
RT_RCDATA 0Ah
RT_MESSAGETABLE 0Bh
На верхнем уровне дерева, значения MAKEINTRESOURCE, показанные выше, помещаются в поле Name соответствующего элемента, идентифицируя тип ресурса. Каждый элемент корневого каталога указывает на потомка во втором уровне дерева. Последние также являются каталогами, содержащие собственные элементы. На этом уровне каталоги используются для идентификации имен ресурсов данного типа. Если бы Вы имели несколько меню в своем приложении, для каждого из них на втором уровне каталога было бы по одному элементу. Ресурсы могут быть идентифицированы либо по имени, либо по целому числу. Они различаются на этом уровне иерархии с помощью поля Name в элементе каталога. Если 31-ый бит поля Name установлен, тогда значение находящееся в битах с 30-ого по 0-ой используются как смещение на структуру IMAGE_RESOURCE_DIR_STRING_U:
Кликните здесь для просмотра всего текста
C
1
2
3
4
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
    WORD  Length;
    WCHAR   NameString[ 1 ];
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
Эта структура содержит двухбайтовое поле длины по имени Length, и следующее за ним имя NameString в кодировке UNICODE.
Если 31-ый бит поля Name сброшен, тогда значение находящееся в битах с 30-ого по 0-ой используются как целый идентификатор ресурса. Рисунок 2 показывает ресурс меню как именованный ресурс и строковую таблицу как ID ресурс.
Кликните здесь для просмотра всего текста

Рисунок 2. Дерево ресурсов
Кликните здесь для просмотра всего текста
Если бы у нас было два ресурса меню, один идентифицируемый по имени, а другой ― по целому числу, в этом каталоге ресурсов появились бы два элемента.
Именованый ресурс располагался бы первым, а за ним ― ресурс, идентифицируемый целым числом. Поля каталога NumberOfNamedEntries и NumberOfIdEntries содержали бы значение 1, означающее наличие одного элемента соответствующего типа идентификации.
Ниже второго уровня дерево ресурсов более не ветвится. Первый уровень ветвей каталога представляет тип ресурса, второй уровень ― идентификаторы объектов ресурсов. Третий уровень представляет собой соответствие между индивидуально идентифицированным ресурсом и его соответствующим ID языка. Для обозначения языка ресурса используется поле Name элемента каталога, причем оно идентифицирует и главный язык, и ID диалекта языка ресурса. Win32 SDK для Windows NT содержит перечень значений по умолчанию языков ресурсов. Для значения 0409h, 09 означает главный язык LANG_ENGLISH, а 04 определен как диалект SUBLANG_ENGLISH_CAN. Весь набор идентификаторов языков определен в файле заголовков WINNT.H, составной части Win32 SDK для Windows NT.
Так как элемент идентификации языка является последним элементом в дереве, поле OffsetToData этой структуры является смещением на конечный узел ― структуру IMAGE_RESOURCE_DATA_ENTRY, упоминавшуюся ранее.
Возвращаясь назад к рисунку 2, Вы можете увидеть по одному элементу данных на каждый узел в директории языков. Этот элемент содержит просто размер данных ресурса и относительный виртуальный адрес, по которому они расположены.
Одно из преимуществ в существовании столь сложной структуры в секции ресурсов «.rsrc» ― это то, что Вы можете манипулировать различной информацией из секции без обращения непосредственно к данным ресурсов. Например, Вы можете подсчитать, сколько у Вас ресурсов каждого типа, сколько ресурсов (и есть ли такие) используют некоторый язык, существует ли какой-либо ресурс, и каков размер ресурсов некоторого типа.
Секция экспортируемых данных, «.edata»
Секция «.edata» содержит экспортируемые данные для приложения или DLL. Когда она присутствует, эта секция содержит каталог для манипуляций информацией об экспортируемых данных
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _IMAGE_EXPORT_DIRECTORY {
    ULONG   Characteristics;
    ULONG   TimeDateStamp;
    WORD  MajorVersion;
    WORD  MinorVersion;
    ULONG   Name;
    ULONG   Base;
    ULONG   NumberOfFunctions;
    ULONG   NumberOfNames;
    PULONG  *AddressOfFunctions;
    PULONG  *AddressOfNames;
    PWORD *AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
Поле Name в этом каталоге идентифицирует имя исполнимого модуля. Поля NumberOfFunctions и NumberOfNames содержат количество функций и имен функций, экспортируемых из модуля.
Поле AddressOfFunctions ― это смещение на список указателей экспортируемых функций. Поле AddressOfNames указывает на начало списка имен экспортируемых функций, разделенных нулями. AddressOfNameOrdinals ― смещение на список двухбайтовых целых чисел ― номеров экспортируемых функций.
Эти три AddressOf... поля ― относительные виртуальные адреса в адресном пространстве процесса, в которое был загружен данный файл. После загрузки модуля, относительные виртуальные адреса нужно увеличить на базовый адрес загрузки модуля для получения точных адресов в адресном пространстве загрузившего процесса. Однако, перед загрузкой файла, эти адреса могут быть определены вычитанием из соответствующего поля AddressOf... виртуального адреса заголовка секции (VirtualAddress) и добавлением к результату смещения на тело секции (PointerToRawData) ― полученное значение можно использовать как смещение в файле.
Секция отладочной информации, «.debug»
Отладочная информация первоначально помещается в секцию «.debug». Формат PE-файлов также поддерживает отдельные отладочные файлы (обычно с расширением .DBG), содержащие централизованно всю отладочную информацию. Отладочная секция содержит информацию для отладки, однако каталоги отладочной информации расположены в сегменте «.rdata», описанном выше. Каждый из этих каталогов ссылается на отладочную информацию в секции «.debug».
Структура IMAGE_DEBUG_DIRECTORY каталога отладочной информации определена ниже:
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
6
7
8
9
10
typedef struct _IMAGE_DEBUG_DIRECTORY {
    ULONG   Characteristics;
    ULONG   TimeDateStamp;
    WORD  MajorVersion;
    WORD  MinorVersion;
    ULONG   Type;
    ULONG   SizeOfData;
    ULONG   AddressOfRawData;
    ULONG   PointerToRawData;
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
Секция разделена на отдельные части данных, представляющих различные типы отладочной информации. Для каждого из них имеется каталог отладочной информации, описанный выше.
Кликните здесь для просмотра всего текста
тип отладочной информации значение
IMAGE_DEBUG_TYPE_UNKNOWN0
IMAGE_DEBUG_TYPE_COFF 1
IMAGE_DEBUG_TYPE_CODEVIEW 2
IMAGE_DEBUG_TYPE_FPO 3
IMAGE_DEBUG_TYPE_MISC 4
Поле Type в каждом каталоге определяет, какой тип отладочной информации содержится в данном каталоге. Из вышеприведенного списка можно увидеть, что формат PE-файла поддерживает множество различных типов отладочной информации, так же как и некоторые другие информационные поля. Один из типов, IMAGE_DEBUG_TYPE_MISC ― уникален. Этот тип был добавлен, чтобы содержать различную информацию об исполнимом файле, которая не может быть отнесена к каким-либо более структурированным секциям данных в PE-файле. Это единственное место во всем PE-файле, где обязательно должно быть имя самого файла. Если есть экспортируемые данные, то имя файла будет включено и в секцию экспортируемых данных.
Каждый тип отладочной информации имеет собственную структуру заголовка, определяющего его данные. Каждый из них определен в файле заголовков WINNT.H.
Одно из приятных обстоятельств ― то, что два поля структуры IMAGE_DEBUG_DIRECTORY идентифицируют отладочную информацию. Первое из них, AddressOfRawData ― относительный виртуальный адрес данных после
загрузки файла. Другое, PointerToRawData ― это смещение в PE-файле, по которому находятся эти данные. Это значительно облегчает извлечение любой отладочной информации.
Структура отладочного каталога позволяет относительно легко обращаться к любой отладочной информации. После получения структуры IMAGE_DEBUG_MISC, извлечение имени файла настолько же просто, как вызов функции CopyMemory.
Отладочная информация может быть перемещена в отдельный DBG-файл. Windows NT SDK включает утилиту REBASE.EXE, служащую для этих целей. Например, следующая команда удалит отладочную информацию из исполнимого файла TEST.EXE:
Код
rebase -b 40000 -x c:\samples\testdir test.exe
Отладочная информация будет помещена в новый файл TEST.DBG, расположенный в указанном каталоге, в данном случае в c:\samples\testdir. В начале этого файла расположена структура IMAGE_SEPARATE_DEBUG_HEADER, за ней ― копии заголовков сегментов, существующих в новом исполнимом файле. За заголовками сегментов расположены данные сегмента «.debug». Таким образом, за заголовками сегментов расположена серия структур IMAGE_DEBUG_DIRECTORY и соответствующие им данные. Собственно отладочная информация сохраняет ту же структуру, что и описанная выше отладочная информация обычного PE файла.
______________________________________
Для написания данной главы использовались работы следующих авторов:
  1. Bill / TPOC. От зеленого к красному: Глава 2: Формат исполняемого файла ОС Windows. PE32 и PE64. Способы заражения исполняемых файлов.
  2. Hard Wisdom. ФОРМАТ ИСПОЛНЯЕМЫХ ФАЙЛОВ PortableExecutables (PE).
  3. LUEVELSMEYER. The PE file format.
  4. Randy Kath. Исследование переносимого формата исполнимых файлов «сверху вниз».
  5. Крис Касперски. Путь воина ― внедрение в pe/coff файлы.
  6. Максим М. Гумеров aka MirkWood. Загрузчик PE-файлов. Исследование формата Portable Executable, сопровождающееся написанием PE-загрузчика. Опубликовано в RSDN 20.03.2003
  7. Румянцев П.В. Исследование программ Win32: до дизассемблера и отладчика. ― М.: Горячая линия-Телеком. 2004. ― 367 с.: ил.
  8. Рустэм Галеев aka Roustem. Приложение Windows «голыми руками»
  9. Microsoft Portable Executable and Common Object File Format Specification
______________________________________
© Mikl___ 2013
15
Миниатюры
Сам себе Iczelion   Сам себе Iczelion   Сам себе Iczelion  

Mikl___
Заблокирован
Автор FAQ
04.01.2013, 12:24  [ТС] #8
Win32 API. Урок 2d. А что находится внутри функции MessageBox или как еще можно создать MessageBox?

Помните сказку о лягушке-царевне? Что необходимо было сделать, чтобы убить Кащея-бессмертного?
нужно было сперва с кучей приключений попасть на некий остров, на острове найти дуб, с дуба снять ларец, в ларце ― заяц, в зайце ― утка, в утке ― яйцо, в яйце ― игла, а на кончике иглы ― смерть Кащеева...
Казалось бы, что может быть проще функции MessageBox? Но мы в погоне за минимальным размером постараемся узнать, а что находится внутри MessageBox? Может быть это поможет сделать наши программы еще меньше. Запускаем программу
выводящую MessageBox на экран в дебаггере Ollydbg и в тот момент, когда курсор доходит до строки call MessageBoxA нажимаем не на F8 (Step over), а на клавишу F7 (Step into) и оказываемся внутри функции MessageBoxA ― оказывается здесь происходит перекодировка ASCII-строк заголовка и текста сообщений в UNICODE и вызов функции MessageBoxW, снова жмем на F7 ― оказывается внутри MessageBoxW функция MessageBoxExA1) которая имеет на один параметр больше, чем MessageBoxA и MessageBoxW и этот параметр (dwLanguageId) равен 0 (LANG_NEUTRAL), остальные же параметры совпадают полностью. Внутри MessageBoxExA находится функция MessageBoxExW. Внутри MessageBoxExW ― функция MessageBoxTimeoutA2) у которой на один параметр больше, чем у MessageBoxExW и этот параметр (Timeout) равен -1. Внутри MessageBoxTimeoutA, как можно было догадаться MessageBoxTimeoutW. Внутри MessageBoxTimeoutW - MessageBoxIndirectA3) эта функция позволяет поменять иконку у MessageBox и может вызвать Справку, но мы оставляем эти параметры с нулевым значением. Внутри MessageBoxIndirectA находится точно такая же но с UNICODE строками функция MessageBoxIndirectW SoftModalMessageBox4) У Indy/Clerk я нашел ссылку на NtRaiseHardError5) ― вызов MessageBox на уровне ядра
Традиционный способ определения Unicode-строки:
Assembler
1
usz dw 'U', 'n', 'i', 'c', 'o', 'd', 'e', ' ', 's', 't', 'r', 'i', 'n', 'g', 0
Меня этот способ не устраивал, поэтому, я написал для этой цели макрос du (define unicode string), чтобы без проблем переводить ASCII-строки в UNICODE-строки
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
du  macro string
local bslash
bslash = 0
irpc c,<string>
if bslash eq 0
    if '&c' eq "\";;управляющая последовательность символов
    bslash = 1
    elseif '&c' eq "ё"
    db 51h,4
    elseif '&c' eq "Ё"
    db 1,4
    elseif '&c' gt 127
    db ('&c'- 0B0h),4;;кириллица
    else
    dw '&c'          ;;латиница
    endif
else
bslash = 0
    if '&c' eq "n"    ;;  \n = новая строка
        DW 0Dh,0Ah
        elseif '&c' eq "\";;  \\ = обратная косая черта (\)
        dw '\'
        elseif '&c' eq "r";;  \r = возврат каретки
        dw 0Dh
        elseif '&c' eq "l";;  \l = LF
        dw 0Ah
        elseif '&c' eq "s"
        dw 20h
        elseif '&c' eq "c"
        dw 3Bh
        elseif '&c' eq "t";;  \t = табуляция*
        dw 9
    endif
endif
endm
dw 0
endm
этот макрос превращает ASCII-символы латиницы в UNICODE-символы добавлением после кода символа нуля, а для кириллицы заменяет 0 на 4 и сдвигает код символа на 176, в конце UNICODE-строки ставится ноль, терминирующий строку. Всё, что требуется от пользователя ― после du написать строку в угловых скобках. То есть представить Unicode-строку так:
Assembler
1
usz: du <Unicode string>
обратите внимание, что после названия строки стоит двоеточие.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
; masm windows gui #
.686P
.model flat
;include windows.inc
include native.inc
include ntstatus.inc
includelib user32.lib
includelib ntdll.lib
extern _imp__MessageBoxA@16:dword
extern _imp__MessageBoxW@16:dword
extern _imp__MessageBoxExA@20:dword
extern _imp__MessageBoxExW@20:dword
extern _imp__MessageBoxTimeoutA@24:dword
extern _imp__MessageBoxTimeoutW@24:dword
extern _imp__SoftModalMessageBox@4:dword
extern _imp__MessageBoxIndirectA@4:dword
extern _imp__MessageBoxIndirectW@4:dword
extern _imp__NtRaiseHardError@24:dword
;extern _imp__KiFastSystemCall@0:dword
MB_ICONASTERISK     equ 40h 
MB_OK           equ 0
; structures
MSGBOXPARAMSA STRUCT
  cbSize                DWORD      ?
  hwndOwner             DWORD      ?
  hInstance             DWORD      ?
  lpszText              DWORD      ?
  lpszCaption           DWORD      ?
  dwStyle               DWORD      ?
  lpszIcon              DWORD      ?
  dwContextHelpId       DWORD      ?
  lpfnMsgBoxCallback    DWORD      ?
  dwLanguageId          DWORD      ?
MSGBOXPARAMSA ENDS
 
MSGBOXDATA struct       
     params              MSGBOXPARAMSA <>
     pwndOwner           DWORD ?
     wLanguageId         DWORD ?
     pidButton           DWORD ?         ; // Array of button IDs
     ppszButtonText      DWORD ?         ; // Array of button text strings
     cButtons            DWORD ?
     DefButton           DWORD ?
     CancelId            DWORD ?
     Timeout             DWORD ?
MSGBOXDATA ends
; macros
du  macro string
    irpc c,<string>
    db '&c',0
    endm
    dw 0
    endm
.code
start:  xchg eax,ebx
    mov esi,ebx
    push MB_ICONASTERISK or MB_OK
    push offset MsgCaption
    push [Msg+esi*4]
    push ebx
    call _imp__MessageBoxA@16
;---------------------------------------
    inc esi
    push MB_ICONASTERISK or MB_OK
    push offset TitleText
    push [Msg+esi*4]
    push ebx
    call _imp__MessageBoxW@16
;----------------------------------------
    inc esi
    push ebx
    push MB_ICONASTERISK or MB_OK
    push offset MsgCaption
    push [Msg+esi*4]
    push ebx
    call _imp__MessageBoxExA@20
;-----------------------------------------
    inc esi
    push ebx
    push MB_ICONASTERISK or MB_OK
    push offset TitleText
    push [Msg+esi*4]
    push ebx
    call _imp__MessageBoxExW@20
;-----------------------------------------
    inc esi
        push -1
        push ebx
        push MB_ICONASTERISK or MB_OK
        push offset MsgCaption
        push [Msg+esi*4]
        push ebx
        call _imp__MessageBoxTimeoutA@24
;------------------------------------------
    inc esi
        push -1
        push ebx
        push MB_ICONASTERISK or MB_OK
        push offset TitleText
        push [Msg+esi*4]
        push ebx
        call _imp__MessageBoxTimeoutW@24
;------------------------------------------
        sub esp,sizeof(MSGBOXPARAMSA);40
    mov edi,esp
    assume edi:ptr MSGBOXPARAMSA
    mov dword ptr [edi].cbSize,sizeof(MSGBOXPARAMSA)
    mov dword ptr [edi].hwndOwner,ebx
    mov dword ptr [edi].hInstance,400000h
    mov dword ptr [edi].lpszText,offset Msg8
    mov dword ptr [edi].lpszCaption,offset MsgCaption
    mov dword ptr [edi].dwStyle,MB_ICONASTERISK or MB_OK;:= MB_USERICON;
    mov dword ptr [edi].lpszIcon,ebx;:= PCHAR('MYICO');
    mov dword ptr [edi].dwContextHelpId,ebx;:= 0;
    mov dword ptr [edi].lpfnMsgBoxCallback,ebx;:= nil;
    mov dword ptr [edi].dwLanguageId,ebx;:= LANG_NEUTRAL;   
    push edi
        call _imp__MessageBoxIndirectA@4
;--------------------------------------------------------
    mov dword ptr [edi].cbSize,sizeof(MSGBOXPARAMSA)
    mov dword ptr [edi].hwndOwner,ebx
    mov dword ptr [edi].hInstance,400000h
    mov dword ptr [edi].lpszText,offset Msg9
    mov dword ptr [edi].lpszCaption,offset TitleText
    mov dword ptr [edi].dwStyle,MB_ICONASTERISK or MB_OK
    mov dword ptr [edi].lpszIcon,ebx;:= PCHAR('MYICO');
    mov dword ptr [edi].dwContextHelpId,ebx;:= 0;
    mov dword ptr [edi].lpfnMsgBoxCallback,ebx;:= nil;
    mov dword ptr [edi].dwLanguageId,ebx;:= LANG_NEUTRAL;   
    push edi
        call _imp__MessageBoxIndirectW@4
    add esp,sizeof(MSGBOXPARAMSA)
;-------------------------------------------------------------------------
        sub esp,sizeof MSGBOXDATA+8
;  local mbd:MSGBOXDATA
;  local bufid[1]:DWORD
;  local bufstr[1]:DWORD
    lea eax,[esp+4];bufid
    mov dword ptr[eax],1
    mov [esp],offset _OK
        lea edi,[esp+8]
    mov [edi].cbSize,sizeof MSGBOXPARAMSA
    mov [edi].hwndOwner,ebx
    mov [edi].lpszText,offset Msg7
    mov [edi].lpszCaption,offset TitleText
    mov [edi].dwStyle,MB_ICONASTERISK or MB_OK
        assume edi:ptr MSGBOXDATA
    mov [edi].pwndOwner,ebx;0
    mov [edi].wLanguageId,ebx;0
    mov [edi].pidButton,eax
    mov [edi].ppszButtonText,esp;offset bufstr
    mov [edi].cButtons,1
    mov [edi].DefButton,ebx;0
    mov [edi].CancelId,1
    mov [edi].Timeout,-1
    push edi
    call _imp__SoftModalMessageBox@4
    assume edi:nothing
    add esp,sizeof MSGBOXDATA+8
;------------------------------------------------------------------
ShowMessage proc
Local MessageBuffer[38h]:WCHAR;в Num+Num1 в вордах
Local Message[2]:UNICODE_STRING
Local HardErrorPointer[3]:PVOID
;первый параметр
    lea edi,MessageBuffer
    mov Message.Buffer+sizeof(UNICODE_STRING)*0,edi
    mov Message.MaximumLength+sizeof(UNICODE_STRING)*0,Num
    mov Message._Length+sizeof(UNICODE_STRING)*0,Num-2
    lea edx,Message+sizeof(UNICODE_STRING)*0
    mov HardErrorPointer+sizeof(PVOID)*0,edx;pwText
    mov esi,offset MessageText
    mov ecx,Num+Num1
    rep movsb
;второй параметр
    lea edi,MessageBuffer+Num
    mov Message.Buffer+sizeof(UNICODE_STRING)*1,edi
    mov Message.MaximumLength+sizeof(UNICODE_STRING)*1,Num1
    mov Message._Length+sizeof(UNICODE_STRING)*1,Num1-2
    lea edx,Message+sizeof(UNICODE_STRING)*1
    mov HardErrorPointer+sizeof(PVOID)*1,edx;pwCaption
;третий параметр
        mov HardErrorPointer+sizeof(PVOID)*2,MB_ICONASTERISK+MB_OK;uType
        lea edx,HardErrorPointer
    push offset x;куда функция запишет выбранный юзером ответ (если тип, 
;переданный в следующем параметре, предполагает выбор ответа пользователем)
    push ebx;0 варианты ответов
    push edx;указатель на массив параметров
    push 3;общее число параметров
    push 00000011b;маска, еденичные биты которой соответствуют тем 
;параметрам, которые имеют тип PUNICODE_STRING, а нули ― остальным параметрам
    push STATUS_SERVICE_NOTIFICATION;статус ошибки
    call _imp__NtRaiseHardError@24
;--------------------------------------------------
;меняем первый параметр
    lea edi,MessageBuffer
    mov Message.Buffer+sizeof(UNICODE_STRING)*0,edi
    mov Message.MaximumLength+sizeof(UNICODE_STRING)*0,Num3
    mov Message._Length+sizeof(UNICODE_STRING)*0,Num3-2
    lea edx,Message+sizeof(UNICODE_STRING)*0
    mov HardErrorPointer+sizeof(PVOID)*0,edx;pwText
    mov esi,offset MessageText2
    mov ecx,Num3
    rep movsb
        lea edx,HardErrorPointer
    mov eax,0B6h;ID NtRaiseHardError для WinXP
    push offset x
    push ebx
    push edx
    push 3
    push 00000011b
    push STATUS_SERVICE_NOTIFICATION;статус ошибки
    push offset @f
    mov edx,7FFE0300h
    call dword ptr ds:[edx]
@@:
;--------------------------------------------------
;меняем первый параметр
    lea edi,MessageBuffer
    mov Message.Buffer+sizeof(UNICODE_STRING)*0,edi
    mov Message.MaximumLength+sizeof(UNICODE_STRING)*0,Num4
    mov Message._Length+sizeof(UNICODE_STRING)*0,Num4-2
    lea edx,Message+sizeof(UNICODE_STRING)*0
    mov HardErrorPointer+sizeof(PVOID)*0,edx;pwText
    mov esi,offset MessageText3
    mov ecx,Num4
    rep movsb
        lea edx,HardErrorPointer
    mov eax,0B6h;ID NtRaiseHardError для WinXP
    push offset x
    push ebx
    push edx
    push 3
    push 00000011b
    push STATUS_SERVICE_NOTIFICATION;статус ошибки
    push offset @f
    push offset @f
    mov edx,esp
    db 0Fh,34h;sysenter
@@:
;--------------------------------------------------------
    lea edi,MessageBuffer
    mov Message.Buffer+sizeof(UNICODE_STRING)*0,edi
    mov Message.MaximumLength+sizeof(UNICODE_STRING)*0,Num2
    mov Message._Length+sizeof(UNICODE_STRING)*0,Num2-2
    lea edx,Message+sizeof(UNICODE_STRING)*0
    mov HardErrorPointer+sizeof(PVOID)*0,edx;pwText
    mov esi,offset MessageText1
    mov ecx,Num2
    rep movsb
        lea edx,HardErrorPointer
    mov eax,0B6h;ID NtRaiseHardError для WinXP
    push offset x
    push ebx
    push edx
    push 3
    push 00000011b
    push STATUS_SERVICE_NOTIFICATION;статус ошибки
    mov edx,esp
    int 2Eh
    add esp,18h
    leave
    retn
ShowMessage endp
 
MessageText: du <NtRaiseHardError>
Num = $-MessageText
TitleText: du <Iczelion Tutorial #2:MessageBox>
Num1 = $-TitleText
MessageText1: du <int 2Eh>
Num2 equ $-MessageText1
MessageText2: du <KiFastSystemCall>
Num3 equ $-MessageText2
MessageText3: du <sysenter>
Num4 equ $-MessageText3
x dd 0
Msg dd Msg1,Msg2,Msg3,Msg4,Msg5,Msg6,Msg7,Msg8,Msg9
Msg1    db 'MessageBoxA',0
Msg2:   du <MessageBoxW>
Msg3    db 'MessageBoxExA',0
Msg4:   du <MessageBoxExW>
Msg5    db 'MessageBoxTimeoutA',0
Msg6:   du <MessageBoxTimeoutW>
Msg7:   du <SoftModalMessageBox>
Msg8    db 'MessageBoxIndirectA',0
Msg9:   du <MessageBoxIndirectW>
MsgCaption db "Iczelion Tutorial #2:MessageBox",0
_OK:    du <OK>
end start

MessageBoxEx

Функция MessageBoxEx создает, отображает на экране и оперирует окном сообщений. Окно сообщений содержит определяемое программой сообщение и заголовок, плюс любую комбинацию предопределенных пиктограмм и командных кнопок. Параметр wLanguageId определяет, какой устанавливается ресурс языка, который используется для предопределенных командных кнопок.
Синтаксис
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
6
7
8
int MessageBoxEx
(
    HWND hWnd,      // дескриптор окна владельца
    LPCTSTR lpText,     // адрес текста в окне сообщений
    LPCTSTR lpCaption,  // адрес заголовка в окне сообщений
    UINT uType,         // стиль окна сообщений
    DWORD dwLanguageId  // идентификатор языка
);

Параметры
  • hWnd Идентифицирует окно владельца блока сообщений, которым оно было создано. Если этот параметр имеет значение NULL, у блока сообщения нет окна владельца.
  • lpText Указывает на строку с символом нуля в конце, содержащую сообщение, которое должно быть отражено на экране.
  • lpCaption Указывает на строку с символом нуля в конце, используемую для заголовка диалогового окна. Если этот параметр значение NULL, то по умолчанию используется заголовок Ошибка (Error).
  • uType Определяет установку битов флажков, которые обуславливают содержание и поведение диалогового окна.
Этот параметр может быть комбинацией флажков из ниже следующих групп флажков.
Определите один из следующих флажков, чтобы указать кнопки, содержащиеся в окне сообщений:
  • MB_ABORTRETRYIGNORE ― Окно сообщение содержит три командных кнопки: Прервать (Abort), Повторить (Retry) и Проигнорировать (Ignore).
  • MB_OK ― Окно сообщение содержит одну командную кнопку: OK.
  • MB_OKCANCEL ― Окно сообщение содержит две командных кнопки: OK и Отменить (Cancel).
  • MB_RETRYCANCEL ― Окно сообщение содержит две командных кнопки: Повторить (Retry) и Отменить (Cancel).
  • MB_YESNO ― Окно сообщение содержит две командных кнопки: Да (Yes) и Нет (No).
  • MB_YESNOCANCEL ― Окно сообщение содержит три командных кнопки: Да (Yes), Нет (No) и Отменить (Cancel).
Определите один из следующих флажков, чтобы отобразить иконку в окне сообщений:
  • MB_ICONEXCLAMATION, MB_ICONWARNING ― В окне сообщений появляется пиктограмма восклицательного знака.
  • MB_ICONINFORMATION, MB_ICONASTERISK ― В окне сообщений появляется пиктограмма, состоящая из символа i в круге.
  • MB_ICONQUESTION ― В окне сообщений появляется пиктограмма в виде знака вопроса.
  • MB_ICONSTOP,MB_ICONERROR,MB_ICONHAND ― В окне сообщений появляется пиктограмма в виде стоп-сигнала.
Определите один из следующих флажков, чтобы указать заданную по умолчанию кнопку:
  • MB_DEFBUTTON1 ― Первая кнопка ― основная кнопка. MB_DEFBUTTON1 ― значение по умолчанию, если не определена кнопка MB_DEFBUTTON2, MB_DEFBUTTON3 или MB_DEFBUTTON4.
  • MB_DEFBUTTON2 ― Вторая кнопка ― основная кнопка.
  • MB_DEFBUTTON3 ― Третья кнопка ― основная кнопка.
  • MB_DEFBUTTON4 ― Четвертая кнопка ― основная кнопка.
Определите один из следующих флажков, чтобы указать модальность диалогового окна:
  • MB_APPLMODAL ― Пользователь должен ответить окну сообщений до продолжения работы в окне, которое идентифицировано параметром hWnd. Однако, пользователь может перемещаться в окнах других прикладных программ и работать в этих окнах. В зависимости от иерархии окон в прикладной программе, пользователь может получить возможность, чтобы перемещаться в другие окна в пределах прикладной программы. Все дочерние окна родителя окна сообщений автоматически блокируются, но всплывающие окна ― нет. MB_APPLMODAL ― значение по умолчанию, если не определен флажок ни MB_SYSTEMMODAL ни MB_TASKMODAL.
  • MB_SYSTEMMODAL ― То же самое, что и MB_APPLMODAL за исключением того, что окно сообщений имеет стиль WS_EX_TOPMOST. Используйте системно-модальные окна сообщений, чтобы уведомить пользователя о серьезных, потенциально опасных ошибках, которые требуют немедленного внимания (например, запуск программы при нехватке памяти). Этот флажок не имеет никакого влияния на способность пользователя взаимодействовать с другими окнами, чем те, которые связаны с hWnd.
  • MB_TASKMODAL ― То же самое, что и MB_APPLMODAL за исключением того, что все окна верхнего уровня, принадлежащие текущей задаче, заблокированы, если параметр hWnd имеет значение NULL. Используйте этот флажок, когда вызывающая прикладная программа или библиотека не имеют доступного дескриптора окна, но все еще должно сохранять вводимые данные для других окон в текущей прикладной программе без приостановки работы других прикладных программ.
В дополнение, вы можете устанавливать ниже перечисленные флажки:
  • MB_DEFAULT_DESKTOP_ONLY ― Рабочий стол, в настоящее время принимающий ввод должен быть заданным по умолчанию рабочим столом; иначе, функция не выполняет задачу. Заданный по умолчанию рабочий стол ― первая
    запущенная прикладная программа, после того, как пользователь вошел в систему.
  • MB_HELP ― Прибавляет кнопку Справка (Help) в окно сообщений. Выбор кнопки Help или нажатие F1 генерирует событие появления Справки.
  • MB_RIGHT ― Выравнивание текста справа.
  • MB_RTLREADING ― Отображает на экране сообщение и текст заголовка с использованием порядка записи справа налево для систем письменности на арабском или иврите.
  • MB_SETFOREGROUND ― Окно сообщений становится приоритетным окном. Внутри, Windows для окна сообщений вызывает функцию SetForegroundWindow.
  • MB_TOPMOST ― Окно сообщений создается со стилем окна WS_EX_TOPMOST.
  • MB_SERVICE_NOTIFICATION ― Только для Windows NT: вызывающая программа является обслуживающей по уведомлению пользователя о событии. Функция отображает окно сообщений на текущем активном рабочем столе, даже если никто из пользователей не вошел в систему компьютера.
    Если этот флажок установлен, параметр hWnd должен иметь значение NULL. Это такое окно сообщений, которое может появляться на другом рабочем столе, а не только на том, которое соответствует hWnd.
    Для Windows NT версии 4.0, значение MB_SERVICE_NOTIFICATION изменилось. См. WINUSER.H для старых и новых значений. Windows NT 4.0 обеспечивает совместимость вниз для существующих ранее услуг, при помощи отображения старых значений в новых значениях при реализации MessageBox и MessageBoxEx. Это отображение сделано только для исполнимых программ (.exe), которые имеют номер версии, как установлено компоновщиком, меньше чем 4.0.
    Чтобы сформировать обслуживание, которое использует MB_SERVICE_NOTIFICATION и возможность запускать приложение под Windows NT 3.x или Windows NT 4.0, у Вас есть два способа:
    1. Во время компоновки, определите номер версии меньше чем 4.0; или
    2. Во время компоновки, определите версию 4.0. В во время запуска, используйте функцию GetVersionEx, чтобы проверить системную версию. Тогда при продолжении запуска Windows NT 3.x, используйте MB_SERVICE_NOTIFICATION_NT3X, а для Windows NT 4.0, используйте MB_SERVICE_NOTIFICATION.
  • MB_SERVICE_NOTIFICATION_NT3X ― Только для Windows NT: Это значение соответствует значению, определенному для MB_SERVICE_NOTIFICATION для версии 3.51 Windows NT.
dwLanguageId
Определяет язык, на котором будет отображаться текст, содержащийся в предопределенных командных кнопках. Это значение должно быть в форме, возвращающей макрокомандой MAKELANGID.
Каждый привязанный к конкретной стране выпуск Windows обычно содержит ресурсы только для ограниченного набора языков. Таким образом американская версия предлагает LANG_ENGLISH, французская ― LANG_FRENCH, немецкая ― LANG_GERMAN и японская (а Вы как ожидали?), соответственно, ― LANG_JAPANESE. Каждая версия предлагает LANG_NEUTRAL. Это ограничивает установку значений, которые могут использоваться с параметром dwLanguageId. Перед определением идентификатора языка, Вы должны перечислить территории, которые установлены на системе.
Возвращаемые значения
Если функция завершается успешно, возвращаемое значение ― значение элемента меню отличное от нуля, возвращенное диалоговым окном:
  • IDABORT ― Была выбрана кнопка «Прервать» (Abort).
  • IDCANCEL ― Была выбрана кнопка «Отменить» (Cancel).
  • IDIGNORE ― Была выбрана кнопка «Игнорировать» (Ignore).
  • IDNO ― Была выбрана кнопка «Нет» (No).
  • IDOK ― Была выбрана кнопка «OK».
  • IDRETRY ― Была выбрана кнопка «Повторить» (Retry).
  • IDYES ― Была выбрана кнопка «Да» (Yes).
Если окно сообщений имеет кнопку «Отменить» (Cancel), функция возвращает значение IDCANCEL тогда, когда или нажата клавиша ESC, или выбрана кнопка Cancel. Если у окна сообщений нет кнопки Cancel, нажатие на ESC не имеет никакого эффекта.
Замечания
Когда Вы используете системно-модальное окно сообщений, чтобы указать, что система имеет мало памяти, строки, переданные как параметры lpText и lpCaption не должны быть приняты из файла ресурса, так как попытка загрузить ресурс может потерпеть неудачу.
Когда прикладная программа вызывает MessageBoxEx и устанавливает флажки MB_ICONHAND и MB_SYSTEMMODAL в параметре uType, Win32 API отображает на экране законченное окно сообщений независимо от доступной памяти. Когда эти флажки установлены, Windows ограничивает длину текста окна сообщений до одной строки.
Если Вы создаете окно сообщений, в то время когда присутствует диалоговое окно, дескриптор блока диалога используется как параметр hWnd. Параметр hWnd не должен идентифицировать дочернее окно, типа диалогового окна.
MessageBoxTimeout

Функция MessageBoxTimeout ...
Синтаксис
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
6
7
8
9
int MessageBoxTimeout
(
    HWND hWnd,      // дескриптор окна владельца
    LPCTSTR lpText,     // адрес текста в окне сообщений
    LPCTSTR lpCaption,  // адрес заголовка в окне сообщений
    UINT uType,         // стиль окна сообщений
    DWORD dwLanguageId, // идентификатор языка
    DWORD Timeout       // время
);


MessageBoxIndirect

Функция MessageBoxIndirect создает, отображает и оперирует окном сообщений. Окно сообщений содержит определяемый программой текст сообщения и заголовок, любую пиктограмму, и любую комбинацию предопределенных командных кнопок.
Синтаксис
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
int MessageBoxIndirect
(
    LPMSGBOXPARAMS lpMsgBoxParams   // адрес структуры для параметров
            // окна сообщений
);
Параметры
lpMsgBoxParams
Указатель на структуру MSGBOXPARAMS, которая содержит информацию, используемую для показа на экране окна сообщений.
Возвращаемые значения
Возвращаемое значение нулевое, если не имеется достаточно памяти, чтобы создать окно сообщений.
Если функция завершается успешно, возвращаемое значение ― одно из следующих значений элементов меню, возвращенных диалоговым окном:
  • IDABORT ― Была выбрана аварийная кнопка (Abort).
  • IDCANCEL ― Была выбрана кнопка Прервать (Cancel).
  • IDIGNORE ― Была выбрана кнопка Игнорировать (Ignore).
  • IDNO ― Была выбрана кнопка Нет (No).
  • IDOK ― Была выбрана кнопка Согласен (OK).
  • IDRETRY ― Была выбрана кнопка Повторить (Retry).
  • IDYES ― Была выбрана кнопка Да (Yes).
Если окно сообщений имеет кнопку Отменить (Cancel), функция возвращает значение IDCANCEL, если или нажата клавиша ESC, или кнопка выбрана Cancel. Если окно сообщений не имеет кнопки Cancel, нажатие на ESC не оказывает никакого влияния.
Замечания
Когда Вы используете системно-модальное окно сообщений, чтобы указать, что у системы мало памяти, строки, указанные элементами lpszText и lpszCaption структуры MSGBOXPARAMS не должны быть приняты из файла ресурса, потому что попытка загрузить ресурс может потерпеть неудачу.
Когда прикладная программа вызывает MessageBoxIndirect и устанавливает флажки MB_ICONHAND и MB_SYSTEMMODAL в элементе dwStyle структуры MSGBOXPARAMS, Windows показывает на экране законченное окно сообщений независимо от доступной памяти. Когда эти флажки установлены, Windows ограничивает длину текста в окне сообщений до трех строк. Windows автоматически не разрывает строки, чтобы подстроить их под окно сообщений, такая строка сообщения должна содержать символы перевода каретки, чтобы разрывать строки в соответствующих местах.
Если Вы создаете окно сообщений, в то время, когда присутствует диалоговое окно, используйте дескриптор блока диалога как параметр hWnd. Параметр hWnd не должен идентифицировать дочернее окно, типа органа управления в диалоговом окне.
Windows 95: Система может поддерживать максимум 16,364 дескрипторов окна.

SoftModalMessageBox

Функция SoftModalMessageBox создает, отображает и оперирует окном сообщений. Окно сообщений содержит определяемый программой текст сообщения и заголовок, любую пиктограмму, и любую комбинацию предопределенных командных кнопок, любое количество кнопок и любые надписи на кнопках.
Синтаксис
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
int SoftModalMessageBox
(
    LPMSGBOXDATA lpMsgBoxData   // адрес структуры для параметров
            // окна сообщений
);
Параметры
lpMsgBoxData
Указатель на структуру MSGBOXDATA, которая содержит информацию, используемую для показа на экране окна сообщений.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
MSGBOXPARAMSA STRUCT
  cbSize                DWORD      ?;размер структуры
  hwndOwner             DWORD      ?;
  hInstance             DWORD      ?;
  lpszText              DWORD      ?;адрес текста в окне сообщений
  lpszCaption           DWORD      ?;адрес заголовка в окне сообщений
  dwStyle               DWORD      ?;
  lpszIcon              DWORD      ?;
  dwContextHelpId       DWORD      ?;
  lpfnMsgBoxCallback    DWORD      ?;
  dwLanguageId          DWORD      ?;идентификатор языка
MSGBOXPARAMSA ENDS
 
MSGBOXDATA struct       
     params              MSGBOXPARAMSA <>
     pwndOwner           DWORD ?
     wLanguageId         DWORD ?
     pidButton           DWORD ?         ; // Array of button IDs
     ppszButtonText      DWORD ?         ; // Array of button text strings
     cButtons            DWORD ?
     DefButton           DWORD ?
     CancelId            DWORD ?
     Timeout             DWORD ?
MSGBOXDATA ends


NtRaiseHardError

Синтаксис
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
6
7
8
9
10
11
NtRaiseHardError(
  IN NTSTATUS             ErrorStatus,//статус ошибки
  IN ULONG                NumberOfParameters,/*маска, единичные биты которой 
 соответствуют тем параметрам, которые имеют тип PUNICODE_STRING, 
 а нули -- остальным параметрам*/
  IN PUNICODE_STRING      UnicodeStringParameterMask OPTIONAL,//общее число параметров
  IN PVOID                *Parameters,//указатель на массив параметров
  IN HARDERROR_RESPONSE_OPTION ResponseOption,//варианты ответов
  OUT PHARDERROR_RESPONSE Response/*двойное слово, куда функция запишет 
 выбранный юзером ответ (если тип, переданный в следующем параметре, предполагает 
 выбор ответа пользователем)*/ );

___________________________________
© Mikl___ 2013
10
Thread
programmer
1819 / 329 / 27
Регистрация: 01.06.2011
Сообщений: 2,660
Записей в блоге: 1
04.01.2013, 13:35 #9
[delete]
1
Mikl___
Заблокирован
Автор FAQ
04.01.2013, 14:09  [ТС] #10
Win32 API. Урок 3. простое окно
В этом Уроке мы создадим Windows программы, которая отображает полнофункциональное окно на рабочем столе.

Скачайте файл примера здесь.
ТЕОРИЯ, МАТЬ СКЛЕРОЗА
Windows программы для создания графического интерфейса пользуются WinAPI-функциями. Этот подход выгоден как пользователям, так и программистам. Пользователям это дает то, что они не должны изучать интерфейс каждой новой программы, так как Windows программы похожи друг на друга. Программистам ― выгодно тем, что GUI-функции уже оттестированы и готовы для использования. Обратная сторона ― возросшая сложность программирования. Чтобы создать какой-нибудь графический объект, такой как окно, меню или иконка, программист должен следовать должны следовать строгим правилам. Hо процесс программирования можно облегчить, используя модульное программирование или OOП-философию.
Я коротко изложу шаги, требуемые для создания окна:
  1. Взять хэндл вашей программы (обязательно)
  2. Взять командную строку (не нужно до тех пор, пока программе не потребуется ее проанализировать)
  3. Зарегистрировать класс окна (необходимо, если вы не используете один из предопределенных классов окна, таких как MessageBox или диалоговое окно)
  4. Создайте окно (необходимо)
  5. Отобразите его на экране
  6. Обновить содержимое экрана на окне
  7. Запустите бесконечный цикл, в котором будут проверятся сообщения от операционной системы.
  8. Прибывающие сообщения передаются специальной функции, отвечающая за обработку окна
  9. Выйти из программы, если пользователь закрывает окно.
Как вы можете видеть, структура Windows программы довольно сложна по сравнению с досовской программой. Hо мир Windows разительно отличается от мира DOS'а. Windows программы должны быть способными мирно сосуществовать друг с другом. Они должны следовать более строгим правилам. Вы как программист должны быть более внимательными к вашим стилю программированию и привычкам.
ПРАКТИКА, МАТЬ ШИЗОФРЕНИИ
Hиже приведен исходник нашей программы простого окна. Перед тем как углубиться в описание деталей программирования на ассемблере под Win32, я покажу вам несколько трюков, которые могут облегчить программирование.
  • Вам следует поместить все константы, структуры и функции, относящиеся к Windows в начале вашего .asm файла. Это сэкономит вам много сил и времени. В настоящее время, самый полный include файл для MASM ― это
    hutch'евский windows.inc, который вы можете скачать с его или моей страницы. Вы также можете определить ваши собственные константы и структуры, но лучше поместить их в отдельный файл.
  • Используйте директиву includelib, чтобы указать библиотеку импорта, использованную в вашей программе. Например, если ваша программа вызывает MessageBox, вам следует поместить строку "includelib user32.lib" в начале кода. Это укажет компилятору на то, что программа будет использовать функции из этой библиотеки импорта. Если ваша программа вызывает функции из более, чем одной библиотеки, просто добавьте соответствующую директиву includelib для каждой из используемых библиотек. Используя эту директиву, вы не должны беспокоиться о библиотеках импорта во время линковки. Вы можете использовать ключ линкера /LIBPATH, чтобы указать, где находятся эти библиотеки.
  • Объявляя прототипы WinAPI-функций, структур или констант в вашем подключаемом файле, постарайтесь использовать те же имена, что и в windows include-файлах, причем регистр важен. Это избавит вас от головной боли в будущем.
  • Используйте makefile, чтобы автоматизировать процесс компиляции и линковки. Это избавит вас лишних усилий. (Лично я использую wmake из пакета Watcom C/C++ ― примечание переводчика.)
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib ; функции используемые в данной программе находятся в модулях user32.lib и kernel32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
 
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
 
.DATA                           ; initialized data
ClassName db "SimpleWinClass",0 ; Имя нашего класса окна
AppName db "Our First Window",0 ; Имя нашего окна
 
.DATA?                  ; Hеиницилизируемые данные
hInstance HINSTANCE ?   ; Хэндл нашей программы
CommandLine LPSTR ?
 
.CODE                ; Здесь начинается наш код
start: invoke GetModuleHandle, NULL ; Взять хэндл программы
mov hInstance,eax       ; Под Win32, hmodule==hinstance 
invoke GetCommandLine   ; Взять командную строку. Вы не обязаны 
; вызывать эту функцию ЕСЛИ ваша программа не обрабатывает командную строку.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT  ; вызвать основную функцию
invoke ExitProcess, eax ; Выйти из программы.
; Возвращаемое значение, помещаемое в eax, берется из WinMain'а.
 
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX      ; создание локальных переменных в стеке
    LOCAL msg:MSG
    LOCAL hwnd:HWND
 
    mov   wc.cbSize,SIZEOF WNDCLASSEX   ; заполнение структуры wc
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc  ; регистрация нашего класса окна
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,CmdShow ; отобразить наше окно на рабочем столе
    invoke UpdateWindow, hwnd ; обновить клиентскую область
    .WHILE TRUE   ; Enter message loop
       invoke GetMessage, ADDR msg,NULL,0,0
    .BREAK .IF (!eax)
       invoke TranslateMessage, ADDR msg
       invoke DispatchMessage, ADDR msg
    .ENDW
     mov     eax,msg.wParam ; сохранение возвращаемого значения в eax
     ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY            ; если пользователь закрывает окно
        invoke PostQuitMessage,NULL ; выходим из программы
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; функция обработки окна по умолчанию
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp
end start
Анализ:
Вы можете быть ошарашены тем, что простая Windows программа требует так много кода. Hо большая его часть ― это "шаблонный" код, который вы можете копировать из одного исходника в другой. Или, если вы хотите, вы можете скомпилировать часть этого кода в библиотеку, которая будет использоваться как прологовый и эпилоговый код. Вы можете писать код уже только в функции WinMain. Фактически, это то, что делают C-компилятоp. Они позволяют вам писать WinMain без беспокойства о коде, который должен быть в каждой программе. Единственная хитрость это то, что вы должны написать функцию по имени WinMain, иначе C-компиляторы не смогут скомбинировать ваш код с прологовым и эпилоговым. Такого ограничения нет в ассемблерном программировании. Вы можете назвать эту функцию так как вы хотите. Готовьтесь! Это будет долгий, долгий туториал. Давайте же проанализируем эту программу с начала и до самого конца.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
 
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
Первые три строки обязательны в высшей степени. .386 говорит MASM'у, что намереваемся использовать набор инструкций процессора 80386 в этой программе. .Model flat, stdcall говорит MASM'у, что наша программа будет использовать плоскую модель памяти. Также мы использовать передачу параметров типа STDCALL по умолчанию. Следом идет прототип функции WinMain. Перед тем, как мы вызовем в дальнейшем эту функцию, мы должны сначала определить ее прототип.
Мы должны подключить windows.inc в начале кода. Он содержит важные структуры и константы, которые потребуются нашей программе. Этот файл всего лишь текстовый файл. Вы можете открыть его с помощью любого текстового редактора. Пожалуйста заметьте, что windows.inc не содержит все структуры и константы (пока). Hutch и я работаем над этим. Вы можете добавить в него что-то новое, если этого там нет. Наша программа вызывает WinAPI-функции, находящиеся в user32.dll (CreateWindowEx, RegisterWindowClassEx, например) и kernel32.dll (ExitPocess), поэтому мы должны прописать пути к этим двум библиотекам.
Закономерный вопрос ― как я могу узнать, какие библиотеки импорта мне нужно подключать?
Ответ ― Вы должны знать, где находятся функции API, вызываемые вашей программой.
Например, если вы вызываете WinAPI-функцию содержащуюся в gdi32.dll, вы обязаны подключить gdi32.lib. Это ― подход MASM'а. Подход, применяемый TASM'ом, гораздо проще ― подключите всего лишь одну-единственную библиотеку: import32.lib.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
.DATA
    ClassName db "SimpleWinClass",0
    AppName  db "Our First Window",0
 
.DATA?
    hInstance HINSTANCE ?
    CommandLine LPSTR ?
Далее идет секции "DATA".
В .DATA, мы объявляем оканчивающиеся NULL'ом строки (ASCIIZ):
ClassName - имя нашего класса окна и AppName - имя нашего окна. Заметьте, что обе переменные проинициализированны.
В .DATA? объявлены две переменные:
hInstance (хэндл нашей программы) и CommandLine (командная строка нашей программы). Hезнакомые типы данных ― HINSTANCE и LPSTR - на самом деле новые имена для переменных типа DWORD. Вы можете найти их в windows.inc. Обратите внимание, что все переменные в этой секции неинициализированы, так как они не содержат какое-то определенное значение при загрузке программа, но мы резервируем место на будущее.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
.CODE
start: invoke GetModuleHandle, NULL
  mov    hInstance,eax
  invoke GetCommandLine
  mov    CommandLine,eax
  invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
  invoke ExitProcess,eax
  .....
end start
.CODE содержит все наши инструкции. Код программы должен располагаться между мктками <стартовая метка>: и end <стартовая метка>. Имя метки несущественно. Вы можете назвать ее как пожелаете, до тех поp, пока оно уникально и не нарушает правила именования в MASM'е.
Hаша первая инструкция ― вызов функции GetModuleHandle, чтобы получить хэндл нашей программы. Под Win32, instance хэндл и module хэндл ― одно и тоже. Вы можете воспринимать хэндл программы как ее ID. Он используется как параметр, передаваемый некоторым WinAPI-функциям, вызываемым нашей программой, поэтому неплохая идея ― получить его в самом начале.
Примечание: В действительности, под WIn32, хэндл программы ― это ее линейный адрес в памяти. По возвращению из Win32 функции, возвращаемое ею значение находится в регистре eax. Все другие значения возвращаются через переменные, переданные в параметрах функции.
Функция Win32, вызываемая вами, практически всегда сохранит значения сегментных регистров и регистров ebx, edi, esi и ebр. Содержимое регистров eax, ecx и edx этими функциями не сохраняются, так что не ожидайте, что они значения в этих трех регистрах останутся неизменными после вызова WinAPI-функции.
Следующее важное положение ― при вызове WinAPI-функции возвращают целочисленное значение в регистре eax. Если какая-то из ваших функций будет вызываться Windows, вы также должны играть по правилам: сохранять и восстанавливать значения используемых сегментных регистров и регистров ebx, edi, esi, ebр до выхода из функции, иначе ваша программа очень быстро "повешает" вашу операционную систему.
Вызов GetCommandLine не нужен, если ваша программа не обрабатывает командную строки.
Далее идет вызов WinMain. Она получает четыре параметра: хэндл программы, хэндл предыдущего экземпляра программы, командную строку и состояние окна при первом появлении. Под Win32 нет такого понятия, как предыдущий экземпляр приложения. Каждая программа одна-одинешенька в своем адресном пространстве, поэтому значение переменной hPrevInst всегда равно 0. Это пережиток времен Win16, когда все экземпляры программы запускались в одном и том же адресном пространстве, и экземпляр мог узнать, был ли запущены еще копии этой программы. Под Win16, если hPrevInst равен NULL, тогда этот экземпляр является первым.
Примечание: Вы не обязаны объявлять функцию с именем WinMain. Hа самом деле, вы совершенно свободны в этом отношении. Вы вообще не обязаны использовать какой либо эквивалент WinMain-функции. Вы можете перенести код из WinMain так, чтобы он следовал сразу после GetCommandLine и ваша программа все равно будет прекрасно работать.
По возвращению из WinMain, в регистре eax появится значение кода выхода из программы. Мы передаем код выхода как параметр функции ExitProcess, которая завершает нашу программу.
Кликните здесь для просмотра всего текста
Assembler
1
2
WinMain proc
Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

В вышенаписанной строке объявление функции WinMain. Обратите внимание на параметры. Вы можете обращаться к этим параметрам, вместо того, чтобы манипулировать со стеком. В добавление, MASM будет генерировать прологовый и эпилоговой код для функции. Так что мы не должны беспокоиться о состоянии стека при входе и выходе из функции.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
Директива LOCAL резервирует память из стека для локальных переменных, использованных в функции. Все директивы LOCAL должны следовать непосредственно после директивы PROC. После LOCAL сразу идет <имя_переменной>:<тип переменной>. То есть LOCAL wc:WNDCLASSEX говорит MASM'у зарезервировать память из стека в объеме, равному размеру структуры WNDCLASSEX для переменной wc. Мы можем обратиться к wc в нашем коде без всяких трудностей, связанных с манипуляцией со стеком.
Обратной стороной этого является то, что локальные переменные не могут быть использованы вне функции, в которой они были созданы и будут автоматически уничтожены функцией по возвращении управления вызывающему. Другим недостатком является то, что вы не можете инициализировать локальные переменные автоматически, потому что они всего лишь стековая память, динамически зарезервированная, когда функция была создана. Вы должны вручную присвоить им значения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mov   wc.cbSize,SIZEOF WNDCLASSEX
mov    wc.style, CS_HREDRAW or CS_VREDRAW
mov    wc.lpfnWndProc, OFFSET WndProc
mov    wc.cbClsExtra,NULL
mov    wc.cbWndExtra,NULL
push   hInstance
pop    wc.hInstance
mov    wc.hbrBackground,COLOR_WINDOW+1
mov    wc.lpszMenuName,NULL
mov    wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov    wc.hIcon,eax
mov    wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov    wc.hCursor,eax
invoke RegisterClassEx, addr wc
Все написанное выше в действительности весьма просто. Это инициализация класса окна. Класс окна ― это не что иное, как наметки или спецификации будущего окна. Он определяет некоторые важные характеристики окна, такие как иконка, курсор, функцию, ответственную за окно и так далее. Вы создаете окно из класса окна. Это некоторый сорт концепции ООП. Если вы создаете более, чем одно окно с одинаковыми характеристиками, есть резон для того, чтобы сохранить все характеристики только в одном месте, и обращаться к ним в случае надобности. Эта схема спасет большое количество памяти путем избегания повторения информации. Помните, Windows создавался во времена, когда чипы памяти стоили непомерно высоко и большинство компьютеров имели 1 MB памяти. Windows должен был быть очень эффективным в использовании скудных ресурсов памяти. Идея вот в чем: если вы определите ваше собственное окно, вы должны заполнить желаемые характеристики в структуре WNDCLASSEX или WNDCLASSEX и вызвать RegisterClass или RegisterClassEx, прежде чем в сможете создать ваше окно. Вы только должны один раз зарегистрировать класс окна для каждой их разновидности, из которых вы будете создавать окна.
В Windows есть несколько предопределенных классов, таких как класс кнопки или окна редактирования. Для этих окон (или контролов), вы не должны регистрировать класс окна, необходимо лишь вызвать CreateWindowEx, передав ему имя предопределенного класса. Самый важный член WNDCLASSEX - это lpfnWndProc. lpfn означает дальний
указатель на функцию. Под Win32 нет "близких" или "дальних" указателей, а лишь просто указатели, так как модель памяти теперь FLAT. Hо это опять же пережиток времен Win16. Каждому классу окна должен быть сопоставлена процедура окна, которая ответственна за обработку сообщения всех окон этого класса. Windows будут слать сообщения процедуре окна, чтобы уведомить его о важных событий, касающихся окон, за которые ответственна эта процедура, например о вводе с клавиатуры или перемещении мыши. Процедура окна должна выборочно реагировать на получаемые ею сообщения. Вы будете тратить большую часть вашего времени на написания обработчиков событий.
Hиже объясняется каждый из членов структуры WNDCLASSEX:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
WNDCLASSEX STRUCT DWORD
  cbSize            DWORD      ?
  style             DWORD      ?
  lpfnWndProc       DWORD      ?
  cbClsExtra        DWORD      ?
  cbWndExtra        DWORD      ?
  hInstance         DWORD      ?
  hIcon             DWORD      ?
  hCursor           DWORD      ?
  hbrBackground     DWORD      ?
  lpszMenuName      DWORD      ?
  lpszClassName     DWORD      ?
  hIconSm           DWORD      ?
WNDCLASSEX ENDS
  • cbSize: размер структуры WDNCLASSEX в байтах. Мы можем использовать оператор SIZEOF, чтобы получить это значение.
  • style: Стиль окон, создаваемых из этого класса. Вы можете комбинировать несколько стилей вместе, используя оператор "or".
  • lрfnWndProc: Адрес процедуры окна, ответственной за окна, создаваемых из класса.
  • cbClsExtra: Количество дополнительных байтов, которые нужно зарезервировать (они будут следовать за самой структурой). По умолчанию, операционная система инициализирует это количество в 0. Если приложение использует WNDCLASSEX структуру, чтобы зарегистрировать диалоговое окно, созданное директивой CLASS в файле ресурсов, оно должно приравнять этому члену значение DLGWINDOWEXTRA.
  • hInstance: Хэндл модуля.
  • hIcon: Хэндл иконки. Возвращается WinAPI-функцией LoadIcon.
  • hCursor: Хэндл курсора. Возвращается WinAPI-функцией LoadCursor.
  • hbrBackground: Цвет фона
  • lpszMenuName: Хэндл меню для окон, созданных из класса по умолчанию.
  • lpszClassName: Имя класса окна.
  • hIconSm: Хэндл маленькой иконки, которая сопоставляется классу окна. Если этот член равен NULL'у, система ищет иконку, определенную для члена hIcon, чтобы использовать ее как маленькую иконку.
Кликните здесь для просмотра всего текста
Assembler
1
2
invoke CreateWindowEx, NULL, ADDR ClassName, ADDR AppName, WS_OVERLAPPEDWINDOW,\
         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst, NULL
После регистрации класса окна, мы должны вызвать CreateWindowEx, чтобы создать наше окно, основанное на этом классе. Заметьте, что этой функции передаются следующие параметры.
Кликните здесь для просмотра всего текста
Assembler
1
2
CreateWindowExA proto dwExStyle:DWORD, lpClassName:DWORD, lpWindowName:DWORD, dwStyle:DWORD,  X:DWORD, \
 Y:DWORD, nWidth:DWORD, nHeight:DWORD, hWndParent:DWORD, hMenu:DWORD, hInstance:DWORD, lpParam:DWORD
Давайте посмотрим детальное описание каждого параметра:
  • dwExStyle: Дополнительные стили окна. Это новый параметр, который добавлен в старую функцию CreateWindow. Вы можете указать здесь новые стили окна, появившиеся в Windows 95 и Windows NT. Обычные стили окна указываются в dwStyle, но если вы хотите определить некоторые дополнительные стили, такие как toрmost окно (которое всегда наверху), вы должны поместить их здесь. Вы можете использовать NULL, если вам не нужны дополнительные стили.
  • lрClassName: (Обязательный параметр). Адрес ASCIIZ-строки, содержащей имя класса окна, которое вы хотите использовать как шаблон для этого окна. Это может быть ваш собственный зарегистрированный класс или один из предопределенных классов. Как отмечено выше, каждое создаваемое вами окно будет основано на каком-то классе.
  • lрWindowName: Адрес ASCIIZ-строки, содержащей имя окна. Оно будет показано на заголовке (title bar) окна. Если этот параметр будет равен NULL'у, заголовок будет пустым.
  • dwStyle: Стили окна. Вы можете определить появление окна здесь. Можно передать NULL без проблем, тогда у окна не будет кнопок изменения размеров, закрытия и системного меню. Большого прока от этого окна нет. Самый общий стиль ― это WS_OVERLAPPEDWINDOW. Стиль окна всегда лишь битовый флаг, поэтому вы можете комбинировать различные стили окна с помощью оператора "or", чтобы получить желаемый результат. Стиль WS_OVERLAPPEDWINDOW в действительности комбинация большинства общих стилей с помощью этого метода.
  • X, Y: Координаты верхнего левого угла окна. Обычно эти значения равны CW_USEDEFAULT, что позволяет Windows решить, куда поместить окно.
  • nWidth, nHeight: Ширина и высота окна в пикселях. Вы можете также использовать CW_USEDEFAULT, чтобы позволить Windows выбрать соответствующую ширину и высоту для вас.
  • hWndParent: Хэндл родительского окна (если существует). Этот параметр говорит Windows является ли это окно дочерним (подчиненным) другого окна, и, если так, кто родитель окна. Заметьте, что это не родительско-дочерние отношения в окне MDI (multiрly document interface). Дочерние окна не ограничены границами клиентской области родительского окна. Эти отношения нужны для внутреннего использования Windows. Если родительское окно уничтожено, все дочерние окна уничтожаются автоматически. Это действительно просто. Так как в нашем примере всего лишь одно окно, мы устанавливаем этот параметр в NULL.
  • hMenu: Хэндл меню окна. NULL - если будет использоваться меню, определенное в классе окна. Взгляните на код, объясненный ранее, член структуры WNDCLASSEX lрszMenuName. Он определяет меню "по умолчанию" для класса окна. Каждое окно, созданное из этого класса будет иметь тоже меню по умолчанию, до тех пор пока вы не определите специально меню для какого-то окна, используя параметр hMenu. Этот параметр ― двойного назначения. В случае, если ваше окно основано на предопределенном классе окна, оно не может иметь меню. Тогда hMenu используется как ID этого контрола. Windows может определить действительно ли hMenu - это хэндл меню или же ID контрола, проверив параметр lрClassName. Если это имя предопределенного класса, hMenu - это идентификатор контрола. Если нет, это хэндл меню окна.
  • hInstance: Хэндл программного модуля, создающего окно.
  • lрParam: Опциональный указатель на структуру данных, передаваемых окну. Это используется окнами MDI, чтобы передать структуру CLIENTCREATESTRUCT. Обычно этот параметр установлен в NULL, означая, что никаких данных не передается через CreateWindow(). Окно может получать значение этого параметра через вызов функции GetWindowsLong.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
mov    hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd
После успешного возвращения из CreateWindowsEx, хэндл окна находится в eax. Мы должны сохранить это значение, так как будем использовать его в будущем. Окно, которое мы только что создали, не покажется на экране автоматически. Вы должны вызвать ShowWindow, передав ему хэндл окна и желаемый тип отображения на экране, чтобы оно появилось на рабочем столе. Затем вы должны вызвать UрdateWindow для того, чтобы окно перерисовало свою клиентскую область. Эта функция полезна, когда вы хотите обновить содержимое клиентской области. Вы Тем не менее, можете пренебречь вызовом этой функции.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.WHILE TRUE
   invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
   invoke TranslateMessage, ADDR msg
   invoke DispatchMessage, ADDR msg
.ENDW
Теперь наше окно на экране. Hо оно не может получать ввод из внешнего мира. Поэтому мы должны проинформировать его о соответствующих событиях. Мы достигаем этого с помощью цикла сообщений. В каждом модуле есть только один цикл сообщений. В нем функцией GetMessage последовательно проверяется, есть ли сообщения от Windows. GetMessage передает указатель на на MSG-структуру Windows. Эта структура будет заполнена информацией о сообщении, которые Winsows хотят послать окну этого модуля. Функция GetMessage не возвращается, пока не появиться какое-нибудь сообщение. В это время Windows может передать контроль другим программам. Это то, что формирует схему многозадачности в платформе Win16. GetMessage возвращает FALSE, если было получено сообщение WM_QUIT, что прерывает цикл обработки сообщений и происходит выход из программы. TranslateMessage - это вспомогательная функция, которая обрабатывает ввод с клавиатуры и генерирует новое сообщение (WM_CHAR), помещающееся в очередь сообщений. Сообщение WM_CHAR содержит ASCII-значение нажатой клавиши, с которым проще иметь дело, чем непосредственно со скан-кодами. Вы можете не использовать эту функцию, если ваша программа не обрабатывает ввод с клавиатуры.
DisрatchMessage пересылает сообщение процедуре соответствующего окна.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
mov  eax,msg.wParam
ret
WinMain endp
Если цикл обработки сообщений прерывается, код выхода сохраняется в члене MSG-структуре wParam. Вы можете сохранить этот код выхода в eax, чтобы возвратить его Windows. В настоящее время код выхода не влияет никаким образом на Windows, но лучше подстраховаться и играть по правилам.
Assembler
1
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
Это наша процедура окна. Вы не обязаны называть ее WndProc. Первый параметр, hWnd, это хэндл окна, которому предназначается сообщение. uMsg ― сообщение. Отметьте, что uMsg - это не MSG-структура. Это всего лишь число. Windows определяет сотни сообщений, большинством из которых ваша программа интересоваться не будет. Windows будет слать подходящее сообщение, в случае, если произойдет что-то, относящееся к этому окну. Процедура окна получает сообщение и реагирует на это соответствующим образом. wParam и lParam всего лишь дополнительные параметры, использующиеся некоторыми сообщениями. Некоторые сообщения шлют сопроводительные данные в добавление к самому сообщению. Эти данные передаются процедуре окна в переменных wParam и lParam.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
.IF uMsg==WM_DESTROY
    invoke PostQuitMessage,NULL
.ELSE
    invoke DefWindowProc,hWnd,uMsg,wParam,lParam
    ret
.ENDIF
    xor eax,eax
   ret
WndProc endp
Это ключевая часть ― там, где располагается логика действий вашей программы. Код, обрабатывающий каждое сообщение от Windows ― в процедуре окна. Ваш код должен проверить сообщение, чтобы убедиться, что это именно то, которое вам нужно. Если это так, сделайте все, что вы хотите сделать в качестве реакции на это сообщение, а затем возвратитесь, оставив в eax ноль. Если же это не то сообщение, которое вас интересует, вы ДОЛЖНЫ вызвать DefWindowProc, передав ей все параметры, которые вы до этого получили. DefWindowProc - это WinAPI-функция, обрабатывающая сообщения, которыми ваша программа не интересуется.
Единственное сообщение, которое вы ОБЯЗАНЫ обработать ― это WM_DESTROY. Это сообщение посылается вашему окну, когда оно закрывается. В то время, когда процедура окна его получает, окно уже исчезло с экрана. Это всего лишь напоминание, что ваше окно было уничтожено, поэтому вы должны готовиться к выходу в Windows. Если вы хотите дать шанс пользователю предотвратить закрытие окна, вы должны обработать сообщение WM_CLOSE. Относительно WM_DESTROY ― после выполнения необходимых вам действий, вы должны вызвать PostQuitMessage, который пошлет сообщение WM_QUIT, что вынудит GetMessage вернуть нулевое значение в eax, что в свою очередь, повлечет выход из цикла обработки сообщений, а значит из программы.
Вы можете послать сообщение WM_DESTROY вашей собственной процедуре окна, вызвав функцию DestroyWindow.
______________________________________
© Iczelion, пер. Aquila.
8
Вложения
Тип файла: zip tut03.zip (2.5 Кб, 188 просмотров)
Mikl___
Заблокирован
Автор FAQ
05.01.2013, 14:59  [ТС] #11
Win32 API. Урок 3a. Наш вариант простого окна
В этом Уроке мы создадим Windows программы, которая отображает полнофункциональное окно на рабочем столе. Заголовок и импорт мы создадим сами. Это позволит уменьшить программу выводящую простое окно на экран до 514 байт.
Скачайте файл примера здесь.
Теория ― мать склероза

Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
.586
.model tiny,stdcall
;for WinXP - 514 bytes
include windows.inc
exebase equ 400000h
.code
main:
include capito.asm
;---------------------------------------------------------
start:  xor ebx,ebx;ebx = 0
    mov esi,exebase;400000h; Хэндл нашей программы
    mov edi,offset wTitle+exebase; Имя нашего класса окна
;------------------------------
; заполнение структуры wc и регистрация класса
    invoke RegisterClass,\; регистрация нашего класса окна
;После регистрации класса окна, мы должны вызвать CreateWindowEx, 
;чтобы создать наше окно, основанное на этом классе
    esp,\;адрес структуры WNDCLASSEX
    ebx,\;style: Стиль окон, создаваемых из этого класса. Вы можете 
;комбинировать несколько стилей вместе, используя оператор "or".
    offset window_procedure+exebase,\;lрfnWndProc: Адрес процедуры окна, 
;ответственной за окна, создаваемых из класса
        ebx,\;cbClsExtra
    ebx,\;cbWndExtra
    esi,\;hInstance: Хэндл модуля.
    ebx,\;hIcon: Хэндл иконки
    10011h,\;hCursor: Хэндл курсора 
    COLOR_WINDOW+1,\;hbrBackground: Цвет фона 
    ebx,\;lpszMenuName: Хэндл меню для окон, созданных из класса по умолчанию.
    edi;lpszClassName: Имя класса окна
;--------------------------+
; creating the main window |
;--------------------------+
    push ebx;lрParam: Опциональный указатель на структуру данных, 
;передаваемых окну. Это используется окнами MDI, чтобы передать структуру 
;CLIENTCREATESTRUCT. Обычно этот параметр установлен в NULL, означая, что 
;никаких данных не передается через CreateWindow(). Окно может получать значение 
;этого параметра через вызов функции GetWindowsLong.
    push esi;hInstance: Хэндл программного модуля, создающего окно
    shl esi,9;esi=CW_USEDEFAULT
    invoke CreateWindowEx,\;создать окно
    ebx,\;dwExStyle: Дополнительные стили окна. Это новый параметр, 
;который добавлен в старую функцию CreateWindow. Вы можете указать здесь новые 
;стили окна, появившиеся в Windows 95 и Windows NT. Обычные стили окна указываются 
;в dwStyle, но если вы хотите определить некоторые дополнительные стили, такие 
;как toрmost окно (которое всегда наверху), вы должны поместить их здесь. Вы 
;можете использовать NULL, если вам не нужны дополнительные стили.
    edi,\;lрClassName: (Обязательный параметр). Адрес ASCIIZ строки, 
;содержащей имя класса окна, которое вы хотите использовать как шаблон для этого 
;окна. Это может быть ваш собственный зарегистрированный класс или один из 
;предопределенных классов. Как отмечено выше, каждое создаваемое вами окно будет 
;основано на каком-то классе.
    edi,\;lрWindowName: Адрес ASCIIZ строки, содержащей имя окна. Оно 
;будет показано на title bar'е окно. Если этот параметр будет равен NULL'у, он 
;будет пуст
    WS_OVERLAPPEDWINDOW or WS_VISIBLE,\;dwStyle: Стили окна. Вы можете 
;определить появление окна здесь. Можно передать NULL без проблем, тогда у окна 
;не будет кнопок изменения размеров, закрытия и системного меню. Большого прока 
;от этого окна нет. Самый общий стиль - это WS_OVERLAPPEDWINDOW. Стиль окна всегда 
;лишь битовый флаг, поэтому вы можете комбинировать различные стили окна с помощью 
;оператора "or", чтобы получить желаемый результат. Стиль WS_OVERLAPPEDWINDOW в 
;действительности комбинация большинства общих стилей с помощью этого метода.
    esi,\;высота окна в пикселях
;Обычно эти значения равны CW_USEDEFAULT, что позволяет Windows решить, куда 
;поместить окно. nWidth, nHeight: Ширина и высота окна . Вы можете также использовать 
;CW_USEDEFAULT, чтобы позволить Windows выбрать соответствующую ширину и высоту 
;для вас
    esi,\;ширина окна в пикселях
    esi,\;Y-координата верхнего левого угла окна. 
    esi,\;X-координата верхнего левого угла окна.
    ebx,\;hWndParent: Хэндл родительского окна (если существует). Этот 
;параметр говорит Windows является ли это окно дочерним (подчиненным) другого 
;окна, и, если так, кто родитель окна. Заметьте, что это не родительско-дочерние 
;отношения в окна MDI (multiрly document interface). Дочерние окна не ограничены 
;границами клиентской области родительского окна. Эти отношения нужны для 
;внутреннего использования Windows. Если родительское окно уничтожено, все 
;дочерние окна уничтожаются автоматически. Это действительно просто. Так как в 
;нашем примере всего лишь одно окно, мы устанавливаем этот параметр в NULL
    ebx;hMenu: Хэндл меню окна. NULL - если будет использоваться меню, 
;определенное в классе окна. Взгляните на код, объясненный ранее, член структуры 
;WNDCLASSEX lрszMenuName. Он определяет меню "по умолчанию" для класса окна. 
;Каждое окно, созданное из этого класса будет иметь тоже меню по умолчанию, до 
;тех пор пока вы не определите специально меню для какого-то окна, используя 
;параметр hMenu. Этот параметр - двойного назначения. В случае, если ваше окно 
;основано на предопределенном классе окна, оно не может иметь меню. Тогда hMenu 
;используется как ID этого элемента управления. Windows может определить 
;действительно ли hMenu - это хэндл меню или же ID элемента управления, проверив 
;параметр lрClassName. Если это имя предопределенного класса, hMenu - это 
;идентификатор элемента  управления. Если нет, это хэндл меню окна
    mov ebp,esp
;цикл обработки сообщений
message_loop: invoke GetMessage,ebp,ebx,ebx,ebx
    invoke DispatchMessage,ebp;вернуть управление Windows
    jmp message_loop
;----------------------+
; the window procedure |
;----------------------+
window_procedure: cmp dword ptr [esp+08],WM_DESTROY;cmp uMsg,WM_DESTROY
    je short wmDESTROY
    jmp DefWindowProc+exebase;все сообщения, не обрабатываемые в функции 
;WndProc, направляются на обработку по умолчанию
;;если пользователь закрывает окно
wmDESTROY: invoke ExitProcess,ebx; выходим из программы
;--данные------------------------------------------------------ 
wTitle db 'Iczelion Tutorial #3:A Simple Window in MASM'; Имя нашего окна
;-------------------------------------------------------
import: 
dd 0,0,0,user32_dll
dd user32_table
dd 0,0,0,kernel32_dll
dd kernel32_table
dd 0,0
kernel32_table:
ExitProcess             dd _ExitProcess,0
user32_table:
RegisterClass       dd _RegisterClass
CreateWindowEx          dd _CreateWindowEx
GetMessage              dd _GetMessage
DispatchMessage         dd _DispatchMessage
DefWindowProc           dd _DefWindowProc
                        dw 0
_RegisterClass      db 0,0,'RegisterClassA'      
_CreateWindowEx     db 0,0,'CreateWindowExA'
_GetMessage     db 0,0,'GetMessageA'
_DispatchMessage    db 0,0,'DispatchMessageA'
_DefWindowProc      db 0,0,'DefWindowProcA',0
user32_dll      db 'user32'
_ExitProcess        db 0,0,'ExitProcess',0
kernel32_dll        db 'kernel32'
 
end_import:
end main
Чем наши исходники будут отличаться от классического текста Iczelion'a?
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
.DATA
    ClassName db "SimpleWinClass",0
    AppName  db "Our First Window",0
.DATA?
    hInstance HINSTANCE ?
    CommandLine LPSTR ?
Во-первых: с целью сокращения размера программ у нас не будет сегментов .DATA и .DATA? И данные и код будут размещены в единственной секции .code
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
.code
...
;--данные--------------------------------
wTitle  db 'Iczelion Tutorial #3:A Simple Window in masm'
Во-вторых: стандартное начало и завершение у Iczelion'а
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
.CODE
start:  invoke GetModuleHandle, NULL
  mov    hInstance,eax
  invoke GetCommandLine
  mov    CommandLine,eax
  invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
  invoke ExitProcess,eax
  .....
 
end start
Задумаемся, а что нам здесь нужно? Если не указывать link /BASE:адрес с адресом отличным от 400000h, тогда для ЕХЕ hInstance=400000h и надобности в GetModuleHandle нет. Если мы не обрабатываем командную строку, тогда и GetCommandLine не нужен, что осталось?
Запуск приложения с параметром: SW_SHOWDEFAULT, SW_HIDE, SW_SHOWNORMAL/SW_NORMAL, SW_SHOWMINIMIZED, SW_SHOWMAXIMIZED/SW_MAXIMIZE, SW_SHOWNOACTIVATE, SW_SHOW, SW_MINIMIZE, SW_SHOWMINNOACTIVE, SW_SHOWNA, SW_RESTORE, SW_SHOWDEFAULT, SW_MAX. Все это многообразие передается свойствами запускающего ярлычка программы "Окно" "Обычный размер окна" "Свернутое в значок" "Развернутое на весь экран". ExitProcess мы разместим внутри процедуры окна
Assembler
1
start:  xor ebx,ebx ;ebx = 0
Функции Windows сохраняют неизменными значения регистров EBP, ESP, EBX, EDI, ESI этим мы и воспользуемся ― сохраним значение 0 в регистре EBX и везде будем применять однобайтовую команду push ebx (код 53h) вместо двухбайтовой команды push 0 (код 6A00h)
Большая часть программы это заполнение WNDCLASSA/EX структуры
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 . . .
LOCAL wc:WNDCLASSEX      ; создание локальных переменных в стеке
 . . .
mov   wc.cbSize,SIZEOF WNDCLASSEX
mov    wc.style, CS_HREDRAW or CS_VREDRAW
mov    wc.lpfnWndProc, OFFSET WndProc
mov    wc.cbClsExtra,NULL
mov    wc.cbWndExtra,NULL
push   hInstance
pop    wc.hInstance
mov    wc.hbrBackground,COLOR_WINDOW+1
mov    wc.lpszMenuName,NULL
mov    wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov    wc.hIcon,eax
mov    wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov    wc.hCursor,eax
invoke RegisterClassEx, addr wc
Переворачиваем структуру WNDCLASS "вверх ногами" и помещаем ее в стек, как параметры передаваемые функции RegisterClassA. Значения, возвращаемые функциями LoadCursor и LoadIcon, мы посмотрим заранее. Так как курсор и иконка передаваемые нами соответствуют стандартной стрелочке и стандартной иконке, то их идентификаторы предопределены и не изменяются, то есть набора стандартных системных иконок и курсоров одни и те же, и в Win98 и WinXp. Поэтому мы и не используем LoadCursor и LoadIcon. Последним параметром в стек отправляется содержимое регистра esp указатель на структуру WNDCLASSA
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
; заполнение структуры wc и регистрация класса
    invoke RegisterClass,\; регистрация нашего класса окна
;После регистрации класса окна, мы должны вызвать CreateWindowEx, 
;чтобы создать наше окно, основанное на этом классе
    esp,\;адрес структуры WNDCLASSEX
    ebx,\;style: Стиль окон, создаваемых из этого класса. Вы можете 
;комбинировать несколько стилей вместе, используя оператор "or".
    offset window_procedure+exebase,\;lрfnWndProc: Адрес процедуры окна, 
;ответственной за окна, создаваемых из класса
        ebx,\;cbClsExtra
    ebx,\;cbWndExtra
    esi,\;hInstance: Хэндл модуля.
    ebx,\;hIcon: Хэндл иконки
    10011h,\;hCursor: Хэндл курсора 
    COLOR_WINDOW+1,\;hbrBackground: Цвет фона 
    ebx,\;lpszMenuName: Хэндл меню для окон, созданных из класса по умолчанию.
    edi;lpszClassName: Имя класса окна
Кликните здесь для просмотра всего текста
Assembler
1
2
 invoke CreateWindowEx,NULL, ADDR ClassName, ADDR AppName, WS_OVERLAPPEDWINDOW,\
 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst, NULL
пятым, шестым, седьмым и восьмым параметром в стек помещается значение CW_USEDEFAULT=80000000h, то есть в коде нашей программы четыре раза появляется 5-ти байтовый код команды push 80000000h (6800000080h), но ведь в регистре esi уже есть число 400000h (тем более значение hInst дальше нигде не используется) 400000h*200h=80000000h 200h=512=29 сдвигаем hInst влево на 9 разрядов командой shl esi,9 (код 0C1E609h) и четыре раза помещаем в стек содержимое регистра esi командой push esi (код 56h) и таким образом вместо 4*5=20 байт используем всего 3+4=7 байт (почти в три раза меньше!)
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
        mov esi,exebase ; Хэндл нашей программы
    . . .
;После регистрации класса окна, мы должны вызвать CreateWindowEx, чтобы создать 
;наше окно, основанное на этом классе
    push ebx
    push esi
    shl esi,9
    invoke CreateWindowEx,ebx,edi,edi,\
    WS_OVERLAPPEDWINDOW or WS_VISIBLE,\
    esi,esi,esi,esi,ebx,ebx
После регистрации окна Iczelion (и не только он один) проделывает три операции:
  1. создает окно (функция CreateWindow);
  2. Отображает его на экpане (ShowWindow)
  3. Обновить содеpжимое окна (UpdateWindow)
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
        . . .
    invoke ShowWindow, hwnd,CmdShow ; отобразить наше окно на десктопе
        invoke UpdateWindow, hwnd ; обновить клиентскую область
но, как показала практика, добавление к стилю окна (обычно WS_OVERLAPPEDWINDOW) во время создания окна стиля WS_VISIBLE делает ненужным вызов функций ShowWindow и UpdateWindow. Еще один фокус с CreateWindow ― правильное указание класса окна необходимо, если мы создаем элемент окна, с заранее определенными свойствами (STATIC, BUTTON, EDIT и т.п.) правда в этом случае нам не нужен RegisterClass. Когда мы создаем наше главное окно, то его название может быть любым, в том числе и на русском и на китайском в юникоде. Экономим на этом, и название класса окна, и название имени окна, которое будет показано на title bar'е ― будут одним и тем же.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
    mov edi,offset wTitle+exebase; Имя нашего класса окна и имя окна
    . . .
    invoke CreateWindow,ebx,edi,edi,\
        WS_OVERLAPPEDWINDOW + WS_VISIBLE,\
Модуль цикла сообщений у Iczelion'a:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
LOCAL msg:MSG
 . . .
.WHILE TRUE   ; Enter message loop
       invoke GetMessage, ADDR msg,NULL,0,0
    .BREAK .IF (!eax)
       invoke TranslateMessage, ADDR msg
       invoke DispatchMessage, ADDR msg
    .ENDW
Наш модуль обработки цикла сообщений.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
    mov ebp,esp
message_loop: invoke GetMessage,ebp,ebx,ebx,ebx
    invoke DispatchMessage,ebp
    jmp message_loop
Постойте, закричат нам вслед, а где же место под структуру MSG? А оно в стеке заняло место WNDCLASSA структуры, так как сделав свое дело при регистрации и создании окна, больше мы к этой переменной не обратимся. Обычно к циклу сообщений добавляют еще и TranslateMessage хотя обработка нажатий на клавиши в программе и не предусматривается
Единственное сообщение, которое вы ОБЯЗАНЫ обработать ― это WM_DESTROY. Это сообщение посылается вашему окну, когда оно закрывается. В то время, когда процедура окна его получает, окно уже исчезло с экрана. Это всего лишь напоминание, что ваше окно было уничтожено, поэтому вы должны готовиться к выходу в Windows. Если вы хотите дать шанс пользователю предотвратить закрытие окна, вы должны обработать сообщение WM_CLOSE. Относительно WM_DESTROY ― после выполнения необходимых вам действий, вы должны вызвать PostQuitMessage, который пошлет сообщение WM_QUIT, что вынудит GetMessage вернуть нулевое значение в eax, что в свою очередь, повлечет выход из цикла обработки сообщений, а значит из программы.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
 
    .IF uMsg==WM_DESTROY            ; если пользователь закрывает окно
        invoke PostQuitMessage,NULL ; выходим из программы
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; функция обработки окна по умолчанию
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp
end start
Заглянем в код соответствующий WndProc proc написанный Iczelion'ом
Кликните здесь для просмотра всего текста
Код
.0004002B9: 55         push ebp
.0004002BA: 8BEC       mov ebp,esp
.0004002BC: 837D0C02   cmp dword ptr [ebp+0Ch],02;cmp uMsg,WM_DESTROY
.0004002C0: 7509       jne .0004002CB
.0004002C0: 6A00       push 0                 	;push NULL
.0004002C4: E853000000 call PostQuitMessage   	;invoke PostQuitMessage
.0004002C9: EB15       jmp short .0004002E0
.0004002CB: FF7514     push dword ptr [ebp+14h] ;push lParam
.0004002CE: FF7510     push dword ptr [ebp+10h] ;push wParam
.0004002D1: FF750C     push dword ptr [ebp+0Ch] ;push uMsg
.0004002D4: FF7508     push dword ptr [ebp+08h] ;push hWnd
.0004002D7: E846000000 call DefWindowsProcA     ;invoke DefWindowProc
.0004002DC: C9         leave                    ;ret
.0004002DD: C21000     retn 10h
.0004002E0: 33C0       xor eax,eax             	;xor eax,eax
.0004002E2: C9         leave                   	;ret
.0004002E3: C21000     retn 10h
.0004002E6:
В программе предусмотрена обработка только одного сообщения, поэтому нам не нужен стандартный пролог. Код cmp dword ptr [esp+8],WM_DESTROY (5 байт) короче чем push ebp/mov ebp,esp/cmp [ebp+8],WM_DESTROY (7 байт).
Кликните здесь для просмотра всего текста
Код
.000400107: 837C240802   cmp dword ptr [esp+08],02 ;           cmp uMsg,WM_DESTROY
.00040010C: 7406         je .000400114             ;           je wmDESTROY
.00040010E: FF258F014000 jmp DefWindowProcA        ;           jmp _imp__DefWindowProcA@16
.000400114: 53           push ebx                  ;wmDESTROY: push 0
.000400115: FF1597014000 call ExitProcess          ;           call _imp__ExitProcess
.00040011A:
Стандартная обработка остальных сообщений invoke DefWindowProc, hWnd, uMsg, wParam,lParam/leave/ret 10h (21 байт) заменена на jmp DefWindowProc (6 байт)
Существует методика оптимизации, связанная с переходами и вызовами, которая называется «сращиванием хвостов». Она подразумевает преобразование вызовов в переходы: вызовы по самой своей природе требуют большего времени, чем переходы, поскольку помещают в стек адрес возврата и в результате требуют больше обращений к памяти.
«Сращивание хвостов» ― это просто преобразование команды CALL, непосредственно за которой следует команда RET, в команду JMP. Например, последовательность:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
Proc1 proc near
...
call Proc2
ret
Proc1 endp
 
Proc2 proc near
...
ret
Proc2 endp
преобразуется в более быструю:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
Proc1 proc near
...
jmp Proc2
Proc1 endp
 
Proc2 proc near
...
ret
Proc2 endp
Такая оптимизация приводит к следующему: поскольку адрес команды, вызывающей PROC1, находится в стеке на входе в PROC2, процедура PROC2 возвращается прямо к исходной вызывающей программе, тем самым устраняются лишние команды CALL и RET. Если программа PROC2 физически (в памяти) следует за программой PROC1, то можно обойтись даже без команды JMP PROC2, и за выполнением PROC1 может сразу же следовать PROC2.
Содержимое стека после вызова функции DefWindowProc
Кликните здесь для просмотра всего текста
Адрес в ESP Значение Адрес в BSP
esp адрес возврата из DefWindowProc ebp-10h
esp+04h параметр hWnd ebp-0Ch
esp+08h параметр uMsg ebp-08h
esp+0Ch параметр wParam ebp-04h
esp+10h параметр lParam ebp
esp+14h старое значение EBP ebp+04h
esp+18h адрес возврата из WndProc ebp+08h
esp+1Ch параметр hWnd ebp+0Ch
esp+20h параметр uMsg ebp+10h
esp+24h параметр wParam ebp+14h
esp+28h параметр lParam ebp+18h
Для функции DefWindowProc передаются те же параметры, что и для функции WndProc, поэтому достаточно будет поставить jmp вместо call и ret 10h. Так как функция WndProc была вызвана без пролога, то мы выходим из нее без эпилога "leave". Возврат в eax ноль, на самом деле не требуется (это необходимо только для диалогов).
Итого 2E6h-2B9h=2Dh=45 байтов в процедуре WndProc у Iczelion'a против 11Ah-107h=13h=19 байтов у нас
_____________________________________
© Mikl___ 2013


Win32 API. Урок 03b. простое окно. Меньше некуда.
В этом Уроке мы создадим программу, которая отображает полнофункциональное окно на рабочем столе с размером EXE всего 206 байт. Мы целиком избавимся от секции импорта, а часть кода расположим в тех полях заголовка, которые загрузчик не использует либо их содержимое не является критичным при запуске программы. Так как смотреть по каким адресам система располагает Win-API: RegisterClassA, CreateWindowExA, DefWindowProcA, DispatchMessageA, GetMessageA, ExitProcess не очень удобно, а эти адреса для каждой новой версии user32.dll и kernel32.dll будут разными ― мы создадим «программу-носитель» с традиционно-обычным механизмом импорта, которая получив адреса этих функций у операционной системы разместит эти адреса в CHILD-программе TinyWin206.exe, которая будет создана на диске. Эта программа имеет размер 206 байт и будет выводить полнофункциональное окно

Скачайте файл примера здесь.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
.686P
.model flat
include windows.inc
includelib user32.lib
includelib kernel32.lib
extern _imp__MessageBoxA@16:dword
extern _imp__WriteFile@20:dword
extern _imp__CreateFileA@28:dword
extern _imp__CloseHandle@4:dword
extern _imp__LoadLibraryA@4:dword
extern _imp__CreateWindowExA@48:dword
extern _imp__DefWindowProcA@16:dword
extern _imp__DispatchMessageA@4:dword
extern _imp__GetMessageA@16:dword
extern _imp__RegisterClassA@4:dword
extern _imp__ExitProcess@4:dword
;-----------------------------
image_base=400000h
hdrsize=sections-buffer
SizeOfHeaders=(hdrsize+3) and (-4);SizeOfHeaders=((hdrsize+3)/4)*4
SizeOfImage=SizeOfHeaders+((codesize+3) and (-4));SizeOfImage=((hdrsize+3)/4)*4+((codesize+3)/4)*4
 
.code
start:  xor ebx,ebx
        push MB_ICONINFORMATION OR MB_SYSTEMMODAL;1040h
        push offset szInfoCap 
        push offset namefile
        push ebx
    call _imp__MessageBoxA@16
    mov eax,_imp__LoadLibraryA@4
    sub eax,offset _LoadLibraryA-delta+4
    mov _LoadLibraryA,eax
    mov eax,_imp__RegisterClassA@4
    sub eax,offset _RegisterClassA-delta+4
    mov _RegisterClassA,eax
    mov eax,_imp__CreateWindowExA@48
    sub eax,offset _CreateWindowExA-delta+4
    mov _CreateWindowExA,eax
    mov eax,_imp__DefWindowProcA@16
    sub eax,offset _DefWindowProcA-delta+4
    mov _DefWindowProcA,eax
    mov eax,_imp__DispatchMessageA@4
    sub eax,offset _DispatchMessageA-delta+4
    mov _DispatchMessageA,eax
    mov eax,_imp__GetMessageA@16
    sub eax,offset _GetMessageA-delta+4
    mov _GetMessageA,eax
    mov eax,_imp__ExitProcess@4
    sub eax,offset _ExitProcess-delta+4
    mov _ExitProcess,eax
 
    push ebx    ;NULL   
    push FILE_ATTRIBUTE_ARCHIVE
    push CREATE_ALWAYS
    push ebx
    push FILE_SHARE_READ or FILE_SHARE_WRITE
    push GENERIC_READ or GENERIC_WRITE
    push offset namefile
    call _imp__CreateFileA@28
    mov edi,eax     ;hFile
    push ebx        ;lpOverlapped
    push offset SizeReadWrite   ;lpNumberOfBytesToWrite
    push sizeof_image       ;nNumberOfBytesToWrite
    push offset buffer  ;lpBuffer
    push edi    ;hFile
    call _imp__WriteFile@20
    push edi    ;hFile
    call _imp__CloseHandle@4
QUIT:   retn
buffer:
delta = $ - image_base
    dd 'ZM','EP'
    dw 14Ch ; machine
    dw 0    ; NumberOfSections
 
main:   xor ebx,ebx ; 12 байт - 12 использовано
    mov edi,offset style-delta
    assume edi:ptr WNDCLASSA
        push [edi].lpszClassName;"user32"
    jmp next_1 
 
    dw sections-optional_header ;sizeof_optional_header
    dw 103h ; characteristics
 
optional_header:
    dw 10Bh   ; magic
 
next_1: ; 14 байт
    db 0E8h; call LoadLibraryA
_LoadLibraryA dd 0
    push edi 
    db 0E8h; call RegisterClassA
_RegisterClassA dd 0
    push ebx       
    jmp next_2
 
    dd main-buffer ; entry point
 
next_2: mov ecx,CW_USEDEFAULT; 8 байт - 7 использовано
    jmp next_3
 
    db 'M'
    dd image_base
    dd 4;sectalign
    dd 4;filealign
 
next_3: push [edi].hInstance; 8 байт
    push ebx    
    push ebx
    push ecx
    jmp next_4
 
    dw 4 ; major sub-system version
szWinClass  db 'user32'     ; 6 байт
;%define round(n, r) (((n+(r-1))/r)*r);(n+r-1)&(-r)
    dd 100h;sizeof_image
;round(hdrsize, sectalign)+round(codesize,sectalign) ;
    dd sizeof_image;SizeOfHeaders
;round(hdrsize, filealign) ;sizeof_headers
 
next_4: push ecx; 4 байта - 3 использовано
    jmp next_5
 
    db 'i'
    dw 2 ; subsystem
    db 'kl'
    dd 10000h
    dd 1000h
 
next_5: push ecx;12 байт
        push ecx
    push WS_OVERLAPPEDWINDOW + WS_VISIBLE
        push [edi].lpszClassName
        jmp next_6
 
    dd 0 ; number of directories
sections:
 
next_6: push [edi].lpszClassName
    push ebx
    db 0E8h     ;call CreateWindowExA
_CreateWindowExA dd 0
message_loop: push ebx  ;цикл обработки сообщений             
    push ebx
    push ebx
    push edi 
    db 0E8h     ;call GetMessageA
_GetMessageA dd 0
    push edi
 
    db 0E8h     ;call DispatchMessageA
_DispatchMessageA dd 0
    jmp short message_loop
WndProc:
    cmp dword ptr [esp+8],WM_DESTROY;cmp uMsg,WM_DESTROY
    db 0Fh,85h        ;jne DefWindowProcA
_DefWindowProcA dd 0
WMDESTROY: push ebx
    db 0E8h        ;call ExitProcess
_ExitProcess dd 0
;WNDCLASSA -----------------------------------------------
style       dd CS_HREDRAW or CS_VREDRAW; Стиль нашего окна
lpfnWndProc dd WndProc-delta;Адрес процедуры обработки событий
cbClsExtra  dd 0        ;Это нам не нужно
cbWndExtra  dd 0        ;и это тоже
hInstance   dd image_base;Адрес нашей программы в памяти (Windows всегда её грузит по этому адресу)
hIcon       dd 10003h   ;Иконка окна по умолчанию
hCursor     dd 10011h   ;Курсор окна по умолчанию (здесь указан ID обычной стрелки)
hbrBackground   dd COLOR_WINDOW+1;Фон нашего окна
lpszMenuName    dd 0        ;Меню у нас отсутствует
lpszClassName   dd szWinClass-delta; Указатель на имя нашего класса
;----------------------------------------------------------
sizeof_image=$-buffer-1
codesize = $ - main
szInfoCap db "Creator tiny Win Application",0
namefile db 'TinyWin206.exe',0
SizeReadWrite dd 0
end start

________________________________
© Mikl___ 2013


Win32 API. Урок 03c. Окно непрямоугольной формы
В этом Уроке мы создадим программу, которая выводит на экран окно произвольной формы. На рисунке представлена картинка с фиолетовым фоном. Мы создадим окно, которое будет содержать это изображение, фиолетовый цвет сделаем прозрачным, при этом окно примет форму картинки
Кликните здесь для просмотра всего текста

Маска для будущего окна
Скачайте файл примера здесь.
Теория - мать склероза
Каждое окно имеет свою область, которая по умолчанию создается прямоугольной. В WinAPI существует функция SetWindowRgn, которая позволяет задать форму этой области. Сначала вы создаете область, потом устанавливаете ее как форму для окна. Создать область можно с помощью функций Create...Rgn (CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreatePolyPolygonRgn, CreateRectRgn, CreateRectRgnIndirect, CreateRoundRectRgn). Например, чтобы сделать круглое окно, можно воспользоваться CreateEllipticRgn. Регионы можно создавать сложные, составленные из нескольких примитивов. Они образуются путем комбинирования областей функцией CombineRgn.
Создаем один большой регион, который опишет наше изображение. Программа будет универсальной и вы сможете подставить вместо этой любую другую картинку.
Изображение представляет собой двухмерный массив из точек. Каждая строка это отдельный элемент региона. Мы получаем прямоугольный регион по каждой строке, а потом комбинируем все строки вместе. Алгоритм построения региона выглядит следующим образом:
  1. Сканируем строку и находим первый пиксел, отличный от прозрачного. Это будет начало нашего прямоугольника (координата X1).
  2. Сканируем остаток строки, чтобы найти границу прозрачности (последний непрозрачный пиксел, координата X2). Если прозрачных пикселов нет, то регион строится до конца строки.
  3. Координату Y1, принимаем равной номеру строки, а Y2 ― равной Y 1+1. Таким образом, высота прямоугольника, описывающего одну строку, равна одному пикселу.
  4. Строим регион по найденным координатам.
  5. Переходим к следующей строке.
  6. Объединяем созданные регионы и назначаем их окну.
Практика - сестра шизофрении
В качестве изображения подойдет любой bmp-файл. Его размеры нужно будет подставить в качестве констант bmpWidth и bmpHeight. Например, наша картинка ограничена размерами 200 на 200 пикселов. В самой картинке цвет пиксела с координатами x=0 и y=0 считается прозрачным, поэтому при подготовке изображения учтите это и окрасьте все «прозрачные» области этим цветом. Переменная maskBitmap используется для хранения изображения, а переменная hWnd используется для хранения указателя на окно.
Кликните здесь для просмотра всего текста
Assembler
1
2
    invoke LoadImage,esi,edi,ebx,ebx,ebx,LR_LOADFROMFILE
    mov maskBitmap+exebase,eax
Загружаем картинку функцией LoadImage. Изображение читается из файла, первый параметр равен 400000h, второй ― содержит имя файла, в последнем ― указан флаг LR_LOADFROMFILE. Программа будет искать файл mask.bmp в каталоге Images. Значение из переменной maskBitmap будет использовано в качесте кисти, таким образом наше изображение будет выведено на окно
Assembler
1
    invoke SetWindowLong,eax,GWL_STYLE,ebx
Убираем из окна обрамление и системное меню ― если этого не сделать наше окно будет выглядеть вот так
Кликните здесь для просмотра всего текста
получаем в ebp глубину цвета в пикселях и делим ее на 8 ― в ebp глубина цвета в байтах и умножаем глубину цвета на высоту и ширину рисунка
получаем размер рисунка в байтах.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
    movzx ebp,word ptr [edi].bmBitsPixel
    shr ebp,3; получаем количество байт на один пиксел
    mov bpp+exebase,ebp; bpp = bi.bmBitsPixel / 8
    imul ebp,bmpHeight*bmpWidth; в ebp количество байт в картинке (= байт на один пиксел * высота * ширина)
Функция GetObject извлекает информацию для заданного графического объекта.
Синтаксис
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
int GetObject(
  HGDIOBJ hgdiobj,  // дескриптор графического объекта
  int cbBuffer,     // размер буфера для информации об объекте
  LPVOID lpvObject  // буфер с информацией об объекте
);
Параметры
  • hgdiobj ― Дескриптор представляющего интерес графического объекта. Он может быть дескриптором одного из ниже перечисленных объектов: точечный рисунок, кисть, шрифт, палитра, перо, или независимый от устройства растр созданный при помощи вызова функции.
  • cbBuffer ― Устанавливает число байтов информации, которая будет записана в буфер.
  • lpvObject ― Указатель на буфер, который принимает информацию об заданном графическом объекте.
Выделяем память требуемого размера функцией HeapAlloc и передаем полученный указатель на область памяти в регистр esi. При помощи функции GetBitmapBits получаем битовый массив и самый первый байт этого массива будет содержать «прозрачный» цвет, который мы сохраним в edi
Assembler
1
    mov edi,[esi]
Создаем «пустой» регион, куда будем строчка за строчкой добавлять нашу картинку
Кликните здесь для просмотра всего текста
Assembler
1
2
    invoke CreateRectRgn,ebx,ebx,ebx,ebx; ebx=0
        xchg ebp,eax    ;ebp=ResRgn
указатель на регион будет содержаться в ebp
Функция CombineRgn склеивает два региона hrgnSrc1 и hrgnSrc2, чтобы сделать новый регион hrgnDest, подобно операции:
hrgnDest = hrgnSrc1 + hrgnSrc2. В функции CombineRgn можно использовать набор режимов RGN_. Параметры функции CombineRgn:
  • hrgnDest ― указатель на результирующий регион;
  • hrgnSrc1 ― указатель на первый регион;
  • hrgnSrc2 ― указатель на второй регион;
  • fnCombineMode ― метод комбинирования.
Переменная fnCombineMode задает режим слияния. Таких режимов несколько.
Кликните здесь для просмотра всего текста
режимчисловое значениекомментарий
RGN_AND1регион будет являться пересечением обоих заданных регионов, то есть определяться областью, которая относится и к первому hrgnSrc1, и ко второму региону hrgnSrc2 одновременно
RGN_OR2регион будет являться объединением обоих заданных, то есть будет включать все области, относящиеся и к первому hrgnSrc1, и ко второму региону hrgnSrc2
RGN_XOR3регион будет включать области первого hrgnSrc1 и второго региона hrgnSrc2, исключая все пересечения
RGN_DIFF4регион будет включать в себя только те области первого региона hrgnSrc1, которые не пересекаются со вторым регионом hrgnSrc2
RGN_COPY5регион будет точной копией первого региона hrgnSrc1
RGN_MIN1то же, что и RGN_AND
RGN_MAX5то же, что и RGN_COPY
прикрепляем регион к окну SetWindowRgn(hWnd, hrgnDest, true) и показываем готовое окно функцией ShowWindow(hWnd, SW_SHOWNORMAL)
Если вы сделали всё, что написано выше, вы сможете запустить программу и наслаждаться результатом. Но у нее есть один недостаток ― созданное нами окно не имеет строки заголовка и состоит из одной клиентской части, а значит, его нельзя перемещать по экрану. Но эта проблема решается очень просто. В window_procedure добавляем обработку сообщения WM_LBUTTUNDOWN ― если щелкнули левой кнопкой, по нашему окну, то обманем систему и заставим ее поверить, что щелчок был произведен по заголовку окна, тогда Windows сама будет двигать окно.
Если по нашему окну щелкнули правой клавишей (пришло сообщение WM_RBUTTONDOWN) ― убиваем наше окно.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
.586p
.model tiny
;for WinXP - 1049 bytes
include windows.inc
.code
exebase         equ 400000h
bmpWidth        equ 200
bmpHeight       equ 200
main:
include capito.asm
;----------------------------
start:  xor ebx,ebx
    mov esi,exebase;400000h
    mov edi,offset wTitle+exebase
;------------------------------
; registering the window class 
;------------------------------
    invoke LoadImage,esi,edi,ebx,ebx,ebx,LR_LOADFROMFILE,ebx,edi
    mov maskBitmap+exebase,eax
    invoke CreatePatternBrush,eax
    invoke RegisterClass,esp,ebx,offset window_procedure+exebase,ebx,ebx,\
    esi,ebx,10011h,eax
;--------------------------+
; creating the main window |
;--------------------------+
    push ebx
    push esi
    shl esi,9
    invoke CreateWindowEx,ebx,edi,edi,WS_OVERLAPPEDWINDOW,esi,esi,esi,esi,ebx,ebx
        mov hWnd+exebase,eax
        invoke SetWindowLong,eax,GWL_STYLE,ebx,eax,SW_SHOWNORMAL
;----------------------------------------------------------
    mov edi,offset bi+exebase
    assume edi:ptr BITMAP
; функция GetObject возвращает структуру, которая описывает атрибуты объекта
        invoke GetObject,maskBitmap+exebase,sizeof(BITMAP),edi
        movzx ebp,word ptr [edi].bmBitsPixel
        shr ebp,3
        mov bpp+exebase,ebp; bpp = bi.bmBitsPixel / 8;
        imul ebp,bmpHeight*bmpWidth 
    invoke GetProcessHeap
        invoke HeapAlloc,eax,HEAP_NO_SERIALIZE,ebp
        xchg esi,eax
        invoke GetBitmapBits,maskBitmap+exebase,ebp,esi; получаем размеры битмапы
;-------------------------------------------------
        invoke CreateRectRgn,ebx,ebx,ebx,ebx
        xchg ebp,eax        ;ebp=ResRgn
;-------------------------------------------------
    mov edi,[esi]       ;edi=TransPixel
        xor eax,eax     ;eax = 0
;--------------------------------------------------
a1: mov j+exebase,ebx   ;j=0
a2: mov ecx,j+exebase
    inc j+exebase
    cmp [esi],edi   ;pixel == TransPixel ?
        jz short @f
        test eax,eax    ;eax != 0 ?
        jnz short a3
        mov eax,ecx ;eax=j
        jmp short a3    ;if(pixel != TransPixel && eax == 0)  eax=j;
@@:     test eax,eax    ;eax == 0 ?
        jz short a3
    mov edx,i+exebase       ;if(pixel == TransPixel && eax > 0) 
        inc edx
        push edx    ;edx=i+1
        push ecx    ;ecx=j-1
        push i+exebase
    inc eax
        invoke CreateRectRgn,eax ;hRgn=CreateRectRgn(eax,i,j-1,i+1);
        invoke CombineRgn,ebp,ebp,eax,RGN_OR
    xor eax,eax ;eax = 0
a3:     add esi,bpp+exebase
        cmp byte ptr j+exebase,bmpWidth
        jb short a2
        inc i+exebase
    cmp byte ptr i+exebase,bmpHeight
        jb short a1
;-------------------------------------------------------------
        invoke SetWindowRgn,hWnd+exebase,ebp,1
    invoke ShowWindow;показать окно
    mov ebp,esp
;---------------------------+
; entering the message loop |
;---------------------------+
message_loop: invoke GetMessage,ebp,ebx,ebx,ebx
    invoke DispatchMessage,ebp
    jmp message_loop
;----------------------+
; the window procedure |
;----------------------+
window_procedure:
    mov eax,[esp+8];Msg;cmp Msg,WM_DESTROY
    dec eax
    dec eax
    je short @@WM_DESTROY
        sub eax,WM_LBUTTONDOWN-WM_DESTROY;cmp  eax,WM_LBUTTONDOWN=201h
    jz short @@WM_LBUTTONDOWN
    sub eax,WM_RBUTTONDOWN-WM_LBUTTONDOWN;cmp  eax,WM_RBUTTONDOWN=204h
    je short @@WM_DESTROY
    jmp DefWindowProc+exebase;все сообщения, не обрабатываемые в функции WndProc
@@WM_DESTROY: invoke ExitProcess,ebx
;при нажатии кнопки в рабочей области обманем систему и заставим ее поверить, 
;что щелчок был произведен по заголовку окна, тогда Windows сама будет двигать окно
@@WM_LBUTTONDOWN: invoke PostMessage,dword ptr [esp+10h],WM_NCLBUTTONDOWN,2,dword ptr [esp+10h];lParam
    retn 10h
;---------------------------------------------
align 8
bi  BITMAP <0>
wTitle  db 'Images\mask.bmp'
;-----------------------------------------------------------------------
import: 
hWnd    dd 0
j   dd 0
i   dd 0
    dd user32_dll
    dd user32_table
maskBitmap dd 0
bpp dd 0
    dd 0,kernel32_dll
dd kernel32_table
dd 0,0,0,gdi32_dll
dd gdi32_table
dd 0,0,0,0
 
user32_table:
RegisterClass       dd _RegisterClass
CreateWindowEx          dd _CreateWindowEx
GetMessage              dd _GetMessage
PostMessage     dd _PostMessage
DispatchMessage         dd _DispatchMessage
SetWindowLong       dd _SetWindowLong
SetWindowRgn        dd _SetWindowRgn
ShowWindow      dd _ShowWindow
LoadImage       dd _LoadImage
DefWindowProc           dd _DefWindowProc,0
gdi32_table:
GetObject       dd _GetObject
GetBitmapBits       dd _GetBitmapBits
CombineRgn      dd _CombineRgn
CreateRectRgn       dd _CreateRectRgn
CreatePatternBrush  dd _CreatePatternBrush,0
kernel32_table:
GetProcessHeap      dd _GetProcessHeap
HeapAlloc       dd _HeapAlloc
ExitProcess             dd _ExitProcess
                        dw 0
_LoadImage      db 0,0,'LoadImageA'
_RegisterClass      db 0,0,'RegisterClassA'      
_CreateWindowEx     db 0,0,'CreateWindowExA'
_PostMessage        db 0,0,'PostMessageA'
_SetWindowLong      db 0,0,'SetWindowLongA'
_SetWindowRgn       db 0,0,'SetWindowRgn'
_ShowWindow     db 0,0,'ShowWindow'
_GetMessage     db 0,0,'GetMessageA'
_DispatchMessage    db 0,0,'DispatchMessageA'
_DefWindowProc      db 0,0,'DefWindowProcA',0
user32_dll      db 'user32'
_HeapAlloc      db 0,0,'HeapAlloc'
_GetProcessHeap     db 0,0,'GetProcessHeap'
_ExitProcess        db 0,0,'ExitProcess',0
kernel32_dll        db 'kernel32'
_CombineRgn     db 0,0,'CombineRgn'
_CreateRectRgn      db 0,0,'CreateRectRgn'
_GetBitmapBits      db 0,0,'GetBitmapBits'
_GetObject      db 0,0,'GetObjectA'
_CreatePatternBrush db 0,0,'CreatePatternBrush',0
gdi32_dll       db 'gdi32'
end_import:
END_SECTION:
end main
______________________________________
© Mikl___ 2013
9
Изображения
  
Вложения
Тип файла: zip tut03c.zip (17.8 Кб, 143 просмотров)
Mikl___
Заблокирован
Автор FAQ
05.01.2013, 16:29  [ТС] #12
Win32 API. Урок 4. Отрисовка текста
В этом разделе мы научимся «рисовать текст» в клиентской части окна. А также узнаем о контекстах устройств.
Вы можете скачать исходный код здесь
ТЕОРИЯ — МАТЬ СКЛЕРОЗА
Текст в Windows — это вид GUI объекта. Каждый символ создан из множества пикселей (точек), которые соединены в различные рисунки. Вот почему мы «рисуем» их, а не «пишем». Обычно вы рисуете текст в вашей клиентской области (на самом деле, вы можете рисовать за пределами клиентской области, но это другая история). Помещения текста на экран в Windows разительно отличается от того, как это делается в DOS'е. В DOS'е размер текстового экрана 80x25. Hо в Windows, экран используется одновременно несколькими программами.
Необходимо следовать определенным правилам, чтобы избежать того, чтобы программы рисовали поверх чужой части экрана. Windows обеспечивает это, ограничивая область рисования его клиентской частью. размер клиентской части окна совсем не константа. Пользователь может изменить его в любой момент, поэтому вы должны определять размеры вашей клиентской области динамически. Перед тем, как вы нарисуете что-нибудь на клиентской части, вы должны спросить разрешения у операционной системы. Действительно, теперь у вас нет абсолютного контроля над экраном, как это было в DOS'е. Вы должны спрашивать Windows, чтобы он позволил вам рисовать в вашей собственной клиентской области. Windows определит размер вашей клиентской области, шрифт, цвета и другие графические атрибуты и пошлет хэндл контекста устройства (device context) программе. Тогда вы сможете использовать его как пропуск к рисованию.
Что такое контекст устройства? Это всего структура данных, использующаяся Windows внутренне. Контекст устройства сопоставлен определенному устройству, такому как принтер или видео-адаптер. Для видеодисплея, контекст устройства обычно сопоставлен определенному окну на экране.
Некоторые из значений в этой структуре — это графические атрибуты, такие как цвета, шрифт и т.д. Это значения по умолчанию, которые вы можете изменять по своему желанию.
Они существуют, чтобы помочь снизить загрузку из-за необходимости указывать эти атрибуты при каждом вызове функций GDI.
Когда программе нужно отрисовать что-нибудь, она должна получить хэндл контекста устройства. Как правило, есть несколько путей достигнуть этого.
  • Вызовите BeginPaint в ответ на сообщение WM_PAINT.
  • Вызовите GetDC в ответ на другие сообщения.
  • Вызовите CreateDC, чтобы создать ваш собственный контекст устройства.
Вы должны помнить одну вещь. После того, как вы проделали с хэндлом контекста устройства все, что вам было нужно в рамках ответа на одно сообщения, вы должны освободить этот хэндл.
Нельзя делать так: получить хэндл, обрабатывая одно сообщение, и освободить его, обрабатывая другое.
Windows посылает сообщение WM_PAINT окну, чтобы уведомить его о том, что настало время для перерисовки клиентской области. Windows не сохраняет содержимое клиентской части окна. Взамен, когда происходить ситуация, служащая основанием для перерисовки окна, Windows помещает в очередь сообщений окна WM_PAINT. Окно должно само перерисовать свою клиентскую область. Вы должны поместить всю информацию о том, как перерисовывать клиентскую область в секции WM_PAINT вашей процедуры окна, так чтобы она могла отрисовать всю клиентскую часть, когда будет получено сообщение WM_PAINT. Также вы должны представлять себе, что такое invalid rectangle. Windows определяет invalid rectangle как наименьшую прямоугольную часть окна, которая должна быть перерисована. Когда Windows обнаруживает invalid rectangle в клиентской области окна, оно посылает сообщение WM_PAINT этому окну. В ответ на сообщение, окно может получить структуру PAINTSTRUCT, которая среди прочего содержит координаты invalid rectangle. Вы вызываете функцию BeginPaint в ответ на сообщение WM_PAINT, чтобы сделать неполноценный прямоугольник снова нормальным. Если вы не обрабатываете сообщение WM_PAINT, то по крайней мере вам следует вызвать функцию DefWindowрroc или функцию ValidateRect, иначе Windows будет слать вам WM_PAINT постоянно.
Hиже показаны шаги, которые вы должны выполнить, обрабатывая сообщение WM_PAINT:
  • Получить хэндл контекста устройства с помощью BeginPaint.
  • Отрисовать клиентскую область.
  • Освободить хэндл функцией EndPaint.
Заметьте, что вы не обязаны думать о том, чтобы пометить неполноценные прямоугольники как нормальные, так как это делается автоматически при вызове BeginPaint. Между связкой BeginPaint-EndPaint, вы можете вызвать любую другую графическую функцию, чтобы рисовать в вашей клиентской области. Практически все из них требуют хэндл контекста устройства.
ПРАКТИКА, СЕСТРА ШИЗОФРЕНИИ
Мы напишем программу, отображающую текстовую строку "Win32 asstmble is great and easy!" в центре клиентской области.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
.386
.model flat,stdcall
option casemap:none
 
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
 
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
 
.DATA
ClassName db "SimpleWinClass",0
AppName  db "Our First Window",0
OurText  db "Win32 assembly is great and easy!",0
 
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
 
.CODE
start:    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke GetCommandLine
    mov CommandLine,eax
    invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
    invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND
 
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,WS_OVERLAPPEDWINDOW,\
    CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
        .WHILE TRUE
               invoke GetMessage, ADDR msg,NULL,0,0
               .BREAK .IF (!eax)
               invoke TranslateMessage, ADDR msg
               invoke DispatchMessage, ADDR msg
        .ENDW
        mov     eax,msg.wParam
        ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL hdc:HDC
    LOCAL ps:PAINTSTRUCT
    LOCAL rect:RECT
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_PAINT
        invoke BeginPaint,hWnd, ADDR ps
        mov    hdc,eax
        invoke GetClientRect,hWnd, ADDR rect
        invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
                DT_SINGLELINE or DT_CENTER or DT_VCENTER
        invoke EndPaint,hWnd, ADDR ps
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor   eax, eax
    ret
WndProc endp
end start
Разбор полетов
Большая часть этого кода точно такая же, как и пример из Урока 3. Я объясню только важные изменения.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT
Это несколько переменных, использующихся в нашей секции WM_PAINT. Переменная hdc используется для сохранения хэндла контекста устройства, возвращенного функцией BeginPaint. рs - это структура PAINTSTRUCT. Обычно вам не нужны значения этой структуры. Она передается функции BeginPaint и Windows заполняет ее подходящими значениями. Затем вы передаете рs функции EndPaint, когда заканчиваете отрисовку клиентской области. rect — это структура RECT, определенная следующим образом:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
RECT Struct
   left      LONG ?
   top       LONG ?
   right     LONG ?
   bottom    LONG ?
RECT ends
Left и toр — координаты верхнего левого угла прямоугольника. Right и bottom — координаты нижнего правого угла. Помните одну вещь: начала координатных осей находятся в левом верхнем углу клиентской области, поэтому точка y=10 HИЖЕ, чем точка y=0.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
invoke BeginPaint,hWnd, ADDR ps
mov    hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
       DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps
В ответ на сообщение WM_PAINT, вы вызываете BeginPaint, передавая ей хэндл окна, в котором вы хотите рисовать и неинициализированную структуру типа PAINTSTRUCT в качестве параметров. После успешного вызова, eax содержит хэндл контекста устройства. После вы вызываете GetClientRect, чтобы получить размеры клиентской области. размеры возвращаются в переменной rect, которую вы передаете функции DrawText как один из параметров. Синтаксис DrawText'а таков:
Assembler
1
DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD
DrawText — это высокоуровневая WinAPI-функция вывода текста. Она берет на себя такие вещи как перенос слов, центровка и т.п., так что вы можете сконцентрироваться на строке, которую вы хотите нарисовать. Ее низкоуровневый брат, TextOut, будет описан в следующем Уроке. DrawText подгоняет строку под прямоугольник. Она использует выбранный в настоящее время шрифт, цвет и фон для отрисовки текста. Слова переносятся так, чтобы строка влезла в границы прямоугольника. DrawText возвращает высоту выводимого текста в единицах устройства, в нашем случае в пикселях. Давайте посмотрим на ее параметры:
  • hdc — хэндл контекста устройства
  • lрString — указатель на строку, которую вы хотите нарисовать в прямоугольнике. Строка должна заканчиваться NULL'ом, или же вам придется указывать ее длину в следующем параметре, nCount.
  • nCount — количество символов для вывода. Если строка заканчивается NULL'ом, nCount должен быть равен -1. В противоположном случае, nCount должен содержать количество символов в строке.
  • lрRect — указатель на прямоугольник (структура типа RECT), в котором вы хотите рисовать строку. Заметьте, что прямоугольник ограничен, то есть вы не можете нарисовать строку за его пределами.
  • uFormat — значение, определяющее как строка отображается в прямоугольнике.
    Мы используем три значения, скомбинированные оператором "or":
    • DT_SINGLELINE указывает, что текст будет располагаться в одну строку
    • DT_CENTER — центрирует текст по горизонтали
    • DT_VCNTER — центрирует тест по вертикали. Должен использоваться вместе с DT_SINGLELINE.
После того, как вы отрисовали клиентскую область, вы должны вызвать функцию EndPaint, чтобы освободить хэндл устройства контекста.
Вот и все. Главные идеи этого урока:
  • Вы вызываете связку BeginPaint-EndPaint в ответ на сообщение WM_PAINT. Делайте все, что вам нужно с клиентской областью между вызовами этих двух функций.
  • Если вы хотите перерисовать вашу клиентскую область в ответе на другие сообщения, у вас есть два варианта:
    1. Используйте связку GetDC-ReleaseDC и делайте отрисовку между вызовами этих функций.
    2. Вызовите Invalidaterect или UpdateWindow, чтобы Windows послала сообщение WM_PAINT вашему окну.
__________________________________________________
© Iczelion, пер. Aquila.
7
Вложения
Тип файла: zip tut04.zip (2.8 Кб, 102 просмотров)
Mikl___
Заблокирован
Автор FAQ
05.01.2013, 18:21  [ТС] #13
Win32 API. Урок 4a. Отрисовка текста

Мы перепишем программу, отображающую текстовую строку "Win32 asstmble is great and easy!" в центре клиентской области, уменьшив размеры с 3072 байтов до 652 байта
Скачайте файл примера здесь.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
.586p
.model tiny
;for WinXP - 652 bytes
include windows.inc
.code
exebase         equ 400000h
main:
include capito.asm
;---------------------------------------------------------
start:  xor ebx,ebx
    mov edi,offset wTitle+exebase
        mov esi,exebase
    invoke RegisterClass,esp,CS_HREDRAW or CS_VREDRAW,offset WndProc+exebase,\
        ebx,ebx,esi,ebx,10011h,COLOR_WINDOW+1,ebx,edi
    push ebx       
    push esi
        shl esi,9;esi=CW_USEDEFAULT
    invoke CreateWindowEx,ebx,edi,edi,\ ;создать окно
        WS_OVERLAPPEDWINDOW + WS_VISIBLE,esi,esi,esi,esi,ebx,ebx
    mov ebp,esp
message_loop: invoke GetMessage,ebp,ebx,ebx,ebx;цикл обработки сообщений
    invoke DispatchMessage,ebp;вернуть управление Windows             
    jmp short message_loop
WndProc:
    hWnd     equ ebp+8
    uMsg     equ ebp+0Ch
    enter sizeof(PAINTSTRUCT),0
    mov eax,[uMsg]
    mov edi,[hWnd]
    dec eax; cmp uMsg,WM_DESTROY
    dec eax
    je short wmDESTROY
    sub eax,WM_PAINT-WM_DESTROY; cmp uMsg,WM_PAINT
    je short wmPAINT
    leave
        jmp DefWindowProc+exebase   
wmPAINT: invoke BeginPaint,edi,esp
        lea esi,[esp].PAINTSTRUCT.rcPaint
    invoke DrawText,eax,offset expTxt+exebase,-1,esi,\
        DT_SINGLELINE or DT_CENTER or DT_VCENTER
    invoke EndPaint,edi,esp
    leave
    retn 10h
 
wmDESTROY: invoke ExitProcess,ebx;завершение программы
;--------------------------------------------------------
wTitle db 'Iczelion Tutorial #4:Painting with Text',0
expTxt db 'Win32 assembly with MASM is great and easy'
;-------------------------------------------------------
import: 
dd 0,0,0,user32_dll
dd user32_table
dd 0,0,0,kernel32_dll
dd kernel32_table
dd 0,0
kernel32_table:
ExitProcess             dd _ExitProcess,0
user32_table:
RegisterClass       dd _RegisterClass
CreateWindowEx          dd _CreateWindowEx
GetMessage              dd _GetMessage
DispatchMessage         dd _DispatchMessage
BeginPaint      dd _BeginPaint
EndPaint        dd _EndPaint    
DrawText        dd _DrawText
DefWindowProc           dd _DefWindowProc
                        dw 0
_RegisterClass      db 0,0,'RegisterClassA'      
_CreateWindowEx     db 0,0,'CreateWindowExA'
_GetMessage     db 0,0,'GetMessageA'
_DispatchMessage    db 0,0,'DispatchMessageA'
_BeginPaint     db 0,0,'BeginPaint'
_EndPaint       db 0,0,'EndPaint'
_DrawText       db 0,0,'DrawTextA'
_DefWindowProc      db 0,0,'DefWindowProcA',0
user32_dll      db 'user32'
_ExitProcess        db 0,0,'ExitProcess',0
kernel32_dll        db 'kernel32'
end_import:
end main
ПРАКТИКА — СЕСТРА ШИЗОФРЕНИИ

Iczelion отводит в стеке место под три локальных переменных.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT
Переменная hdc используемая для сохранения хэндла контекста устройства, возвращенного функцией BeginPaint используется всего один раз. Скажу вам больше — структура PAINTSTRUCT заполняемая функцией BeginPaint уже содержит hdc. В варианте Iczelion мы вызываем GetClientRect, чтобы получить размеры клиентской области, размеры возвращаются в переменной rect, которую вы передаете функции DrawText как один из параметров. Но клиентскую область также можно обнаружить в структуре PAINTSTRUCT поэтому нам не нужна функция GetClientRect
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
PAINTSTRUCT STRUCT
  hdc           DWORD      ?;дескриптор DC
  fErase        DWORD      ?;истина, если перерисовывается заполнение окна
  rcPaint       RECT       &lt;&gt;;координаты области перерисовки
  fRestore      DWORD      ?;зарезервировано
  fIncUpdate    DWORD      ?;зарезервировано
  rgbReserved   BYTE 32 dup(?);зарезервировано
PAINTSTRUCT ENDS
Iczelion объявляет процедуру WndProc следующим образом
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL hdc:HDC
    LOCAL ps:PAINTSTRUCT
    LOCAL rect:RECT
что эквивалентно следующему коду
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
WndProc:
    push ebp
    mov ebp,esp
    add esp,-(sizeof(HDC)+sizeof(PAINTSTRUCT)+sizeof(RECT))
    hWnd     equ ebp+8
    uMsg     equ ebp+0Ch
        wParam   equ ebp+10h
    lParam   equ ebp+14h
    rect     equ ebp-sizeof(RECT)
    ps   equ rect-sizeof(PAINTSTRUCT)
    hdc  equ ps-size(HDC)
Причем адрес последней локальной переменной до первого push будет равен содержимому регистра esp. Мы уже разобрались что без переменной hdc можно обойтись, вместо rect нужно использовать поле rcPaint структуры PAINTSTRUCT и это позволит нам обойтись без функции GetClientRect, переменные lParam и wParam не используются в нашей программе
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
WndProc:
    push ebp
    mov ebp,esp
    add esp,- sizeof(PAINTSTRUCT)
    hWnd     equ ebp+8
    uMsg     equ ebp+0Ch
    ps   equ esp
    rect     equ ps+PAINTSTRUCT.rcPaint
Кликните здесь для просмотра всего текста
Код
.00400118: C8400000	enter 40h,000	;4 байта
.0040011C: . . .
--------------------------------------------
.00400118: 55		push ebp
.00400119: 8BEC		mov ebp,esp
.0040011B: 83C4С0	add esp,-40h	;6 байт
.0040011E: . . .
--------------------------------------------
.0040012E: 57		push edi	;edi=hWnd
.0040012F: FF750C	push d,[ebp+0Ch];push hWnd
Трюки, которые помогают минимизировать размер программы
  • так как адрес ps=[esp] поэтому для получения адреса ps при передаче этого адреса в стек мы не будем использовать конструкцию lea reg,ps / push reg а будем писать push esp;
  • разместив содержимое переменной hWnd в регистре edi мы будем экономить по 2 байта при каждом обращении к hWnd;
  • команда enter XX,0 эквивалентна push ebp/mov ebp,esp/add esp,-XX но занимает в полтора раза меньше места;
  • поместив содержимое uMsg в регистр eax и заменив команды cmp uMsg,WM_XX на sub eax,CONST и dec eax мы также добъемся уменьшения размера кода. Причем, чем больше будет сообщений WM_XX тем эта разница будет больше.
Кликните здесь для просмотра всего текста
Код
.0040111A: 837D0C02	cmp d,[ebp+0Ch],2	;cmp uMsg,WM_DESTROY
.0040111E: 7449		je .00040116B           ;je @@WM_DESTROY
.00400120: 837D0C0F	cmp d,[ebp+0Ch],0Fh     ;cmp uMsg,WM_PAINT
.00400124: 7415         je .00040113B           ;je @@WM_PAINT
.00400126: FF7514	push d,[ebp+14h]	;push wParam
.00400129: FF7510	push d,[ebp+10h]        ;push lParam
.0040012C: FF750C	push d,[ebp+0Ch]        ;push uMsg
.0040012F: FF7508	push d,[ebp+08h]        ;push hWnd
.00400132: E816000000	call DefWindowProcA     
.00400137: C9		leave
.00400138: C21000	retn 10h
.0040013B:
--------------------------------------------
.0040011C: 8B450C	mov eax,[ebp+0Ch]	;mov eax,uMsg
.0040011F: 48		dec eax
.00400120: 48		dec eax			;sub eax,WM_DESTROY
.00400121: 743E		je .000400161		;je @@WM_DESTROY
.00400123: 83E80D	sub eax,0Dh		;sub eax,WM_PAINT-WM_DESTROY
.00400126: 7407		je .000400130		;je @@WM_PAINT
.00400128: C9		leave
.0040012A: FF250C024000	jmp DefWindowProcA
.00400130:
__________________________________
© Mikl___ 2013

Win32 API. Урок 5. Больше о тексте

Мы еще немного поэкспериментируем, на этот раз со шрифтом и его цветом.
Вы можете скачать пример здесь.

ТЕОРИЯ — МАТЬ СКЛЕРОЗА
Цветовая система Windows базируется на RGB значениях, R=красный, G=зеленый, B=синий. Если вы хотите указать Windows цвет, вы должны определить желаемый цвет в системе этих трех основных цветов. Каждое цветовое значение имеет область определения от 0 до 255. Например, если вы хотите чистый красный цвет, вам следует использовать 255, 0, 0. Или если вы хотите чистый белый цвет, вы должны использовать 255, 255, 255. Вы можете видеть из примеров, что получение нужного цвета очень сложно, используя эту систему, так что вам нужно иметь хорошее "чувство на цвета", как мешать и составлять их.
Для установки цвета текста или фона, вы можете использовать SetTextColor и SetBkColor, оба из которых требуют хэндл контекста устройства и 32-битное RGB значение. Структура 32-битного RGB значения определена как:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
RGB_value struct
       unused   db 0
       blue       db ?
       green     db ?
       red        db ?
   RGB_value ends
Заметьте, что первый байт не используется и должен быть нулем. Порядок оставшихся байтов перевернут, то есть blue, green, red. Тем не менее, мы не будем использовать эту структуру, так как ее тяжело инициализовать и использовать. Вместо этого мы создадим макрос. Он будет получать три параметра: значения красного, зеленого и синего. Он будет выдавать желаемое 32-битное RGB значение и сохранять его в eax. Макрос определен следующим образом:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
   RGB macro red,green,blue
       xor    eax,eax
       mov  ah,blue
       shl     eax,8
       mov  ah,green
       mov  al,red
   endm
Вы можете поместить этот макрос в include файл для использования в будущем.
Вы можете "создать" шрифт, вызвав CreateFont или CreateFontIndirect. Разница между ними заключается в том, что CreateFontIndirect получает только один параметр: указатель на структуру логического шрифта, LOGFONT.
СreateFontIndirect более гибкая функция из этих двух, особенно если вашей программе необходимо часто менять шрифты. Тем не менее, в нашем примере мы "создадим" только один фонт для демонстрации, поэтому будем делать это через CreateFont. После вызова этой функции, она вернет хэндл фонта, который вы должны выбрать в определенном контексте устройства. После этого, каждая текстовая API функция будет использовать шрифт, который мы выбрали.
ПРАКТИКА, СЕСТРА ШИЗОФРЕНИИ
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
   .386
   .model flat,stdcall
   option casemap:none
 
   WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
 
   include \masm32\include\windows.inc
   include \masm32\include\user32.inc
   include \masm32\include\kernel32.inc
   include \masm32\include\gdi32.inc
   includelib \masm32\lib\user32.lib
   includelib \masm32\lib\kernel32.lib
   includelib \masm32\lib\gdi32.lib
 
   RGB macro red,green,blue
           xor eax,eax
           mov ah,blue
           shl eax,8
           mov ah,green
           mov al,red
   endm
 
   .data
   ClassName db "SimpleWinClass",0
   AppName  db "Our First Window",0
   TestString  db "Win32 assembly is great and easy!",0
   FontName db "script",0
 
   .data?
   hInstance HINSTANCE ?
   CommandLine LPSTR ?
 
   .code
 
    start:       invoke GetModuleHandle, NULL
       mov    hInstance,eax
       invoke GetCommandLine
       mov CommandLine,eax
       invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
       invoke ExitProcess,eax
 
   WinMain proc   hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
       LOCAL wc:WNDCLASSEX
       LOCAL msg:MSG
       LOCAL hwnd:HWND
 
       mov   wc.cbSize,SIZEOF WNDCLASSEX
       mov   wc.style, CS_HREDRAW or CS_VREDRAW
       mov   wc.lpfnWndProc, OFFSET WndProc
       mov   wc.cbClsExtra,NULL
       mov   wc.cbWndExtra,NULL
       push  hInst
       pop   wc.hInstance
       mov   wc.hbrBackground,COLOR_WINDOW+1
       mov   wc.lpszMenuName,NULL
       mov   wc.lpszClassName,OFFSET ClassName
       invoke LoadIcon,NULL,IDI_APPLICATION
       mov   wc.hIcon,eax
       mov   wc.hIconSm,eax
       invoke LoadCursor,NULL,IDC_ARROW
       mov   wc.hCursor,eax
       invoke RegisterClassEx, addr wc
       invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
              WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
              CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
              hInst,NULL
       mov   hwnd,eax
       invoke ShowWindow, hwnd,SW_SHOWNORMAL
       invoke UpdateWindow, hwnd
       .WHILE TRUE
                   invoke GetMessage, ADDR msg,NULL,0,0
                   .BREAK .IF (!eax)
                   invoke TranslateMessage, ADDR msg
                   invoke DispatchMessage, ADDR msg
       .ENDW
       mov     eax,msg.wParam
       ret
   WinMain endp
   WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
       LOCAL hdc:HDC
       LOCAL ps:PAINTSTRUCT
       LOCAL hfont:HFONT
 
       .IF uMsg==WM_DESTROY
           invoke PostQuitMessage,NULL
       .ELSEIF uMsg==WM_PAINT
           invoke BeginPaint,hWnd, ADDR ps
           mov    hdc,eax
           invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
           OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
           DEFAULT_QUALITY,DEFAULT_PITCH or
           FF_SCRIPT,\
           ADDR FontName
           invoke SelectObject, hdc, eax
           mov    hfont,eax
           RGB    200,200,50
           invoke SetTextColor,hdc,eax
           RGB    0,0,255
           invoke SetBkColor,hdc,eax
           invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
           invoke SelectObject,hdc, hfont
           invoke EndPaint,hWnd, ADDR ps
       .ELSE
           invoke DefWindowProc,hWnd,uMsg,wParam,lParam
           ret
       .ENDIF
       xor    eax,eax
       ret
   WndProc endp
   end start
Анализ:
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
           invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
           OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
           DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
           ADDR FontName
CreateFont создает логический шрифт, который наиболее близок к данным параметрам и доступным данным шрифта. Эта функция имеет больше параметров, чем любая другая в Windows. Она возвращает хэндл логического шрифта, который можно выбрать функцией SelectObject. Мы в подробностях обсудим ее параметры.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
   CreateFont proto nHeight:DWORD,\
                               nWidth:DWORD,\
                               nEscapement:DWORD,\
                               nOrientation:DWORD,\
                               nWeight:DWORD,\
                               cItalic:DWORD,\
                               cUnderline:DWORD,\
                               cStrikeOut:DWORD,\
                               cCharSet:DWORD,\
                               cOutputPrecision:DWORD,\
                               cClipPrecision:DWORD,\
                               cQuality:DWORD,\
                               cPitchAndFamily:DWORD,\
                               lpFacename:DWORD
  • nHeight — желаемая высота символов. Ноль значит использовать размер по умолчанию.
  • nWidth — желаемая ширина символов. Обычно этот параметр равен нулю, что позволяет Windows подобрать ширину соответственно высоте. Однако, в нашем примере, "ширина по-умолчанию" делает символы нечитабельными, поэтому я установил ширину равную 16.
  • nEscaрement — указывает ориентацию вывода следующего символа, относительно предыдущего в десятых градусов. Как правило его устанавливают в 0. Установка в 900 вынуждает идти все символы снизу вверх, 1800 — справа налево, 2700 — сверху вниз.
  • nOrientation — указывает насколько символ должен быть повернут в десятых градусов. 900 — все символы будут "лежать" на спине, и далее по аналогии с предыдущим параметром.
  • nWeight — устанавливает толщину линии. Windows определяет следующие размеры:
    Кликните здесь для просмотра всего текста
    FW_DONTCARE 0
    FW_THIN 100
    FW_EXTRALIGHT 200
    FW_ULTRALIGHT 200
    FW_LIGHT 300
    FW_NORMAL 400
    FW_REGULAR 400
    FW_MEDIUM 500
    FW_SEMIBOLD 600
    FW_DEMIBOLD 600
    FW_BOLD 700
    FW_EXTRABOLD 800
    FW_ULTRABOLD 800
    FW_HEAVY 900
    FW_BLACK 900
  • cItalic — 0 для обычных символов, любое другое значение для курсива.
  • cUnderline — 0 для обычных символов, любое другое значение для подчеркнутых.
  • cStrikeOut — 0 для обычных символов, любое другое значение для перечеркнутых.
  • cCharSet — символьный набор шрифта. Обычно должен быть установлен в OEM_CHARSET, который позволяет Windows выбрать системно-зависимый шрифт.
  • cOutрutPrecision — указывает насколько близко должен приближаться шрифт к характеристикам, которые мы указали. Обычно этот параметр устанавливается в OUT_DEFAULT_PRECIS.
  • cCliрPrecision определяет, что делать с символами, которые вылезают за пределы отрисовочного региона.
  • cQuality — указывает качества вывода, то есть насколько внимательно GDI пытаться подогнать атрибуты логического фонта к атрибутам фонта физического. Есть выбор из трех значений: DEFAULT_QUALITY, PROOF_QUALITY и DRAFT_QUALITY.
  • cPitchAndFamily — указывает питч и семейство фонта. Вы должны комбинировать значение питча и семьи с помощью оператора "or".
  • lрFacename — указатель на заканчивающуюся NULL'ом строку, определяющую гарнитуру шрифта.
Вышеприведенное описание, ни в коем случае, не является исчерпывающим. Вам следует обратиться к вашему Win32 API Справочнику за деталями.
Кликните здесь для просмотра всего текста
Assembler
1
2
           invoke SelectObject, hdc, eax
           mov    hfont,eax
После получения хэндла логического шрифта, мы должны выбрать его в контексте устройства, вызвав SelectObject. Функция устанавливает новые GDI-объекты, такие как перья, кисти и шрифты контекст устройства, используемые GDI функциями. SelectObjet возвращает хэндл замещенного объекта в eax, который нам следует сохранить для будущего вызова SelectObject. После вызова SelextObject любая функция вывода текста будет использовать шрифт, который мы выбрали в данном контексте устройства.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
           RGB    200,200,50
           invoke SetTextColor,hdc,eax
           RGB    0,0,255
           invoke SetBkColor,hdc,eax
Используйте макрос RGB, чтобы создать 32-битное RGB значение, которое будет использоваться функциями SetColorText и SetBkColor.
Кликните здесь для просмотра всего текста
Assembler
1
           invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
Вызываем функцию TextOut для отрисовки текста на клиентской области экрана. Будет использоваться ранее выбранные нами шрифт и цвет.
Assembler
1
           invoke SelectObject,hdc, hfont
После этого мы должны восстановить старый шрифт обратно в данном контексте устройства. Вам всегда следует восстанавливать объект, который вы заменили.
________________________________
© Iczelion, пер. Aquila.
8
Вложения
Тип файла: zip tut05.zip (3.1 Кб, 89 просмотров)
Mikl___
Заблокирован
Автор FAQ
05.01.2013, 19:23  [ТС] #14
Win32 API. Урок 5a. Вращающийся текст

А теперь заставим наш текст вращаться.
Кликните здесь для просмотра всего текста
Вы можете скачать пример здесь
ПРАКТИКА, СЕСТРА ШИЗОФРЕНИИ
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
.586p
.model tiny
;for WinXP - 1182 bytes
include windows.inc
.code
exebase         equ 400000h
main:
include capito.asm
;---------------------------------------------------------
start:  xchg ebx,eax
    mov esi,exebase;400000h
    mov edi,offset wTitle+exebase
;------------------------------
; registering the window class 
;------------------------------
    invoke CreateSolidBrush,\
    0FF0000h,\;синяя кисть
    ebx,\;lpszMenuName: Хэндл меню для окон, созданных из класса по умолчанию.
    edi;lpszClassName: Имя класса окна
    invoke RegisterClass,esp,CS_HREDRAW or CS_VREDRAW,\
        offset window_procedure+exebase,ebx,ebx,esi,ebx,10011h,eax
; +--------------------------+
; | creating the main window |
; +--------------------------+
    push ebx
    push esi
    shl esi,9
    invoke CreateWindowEx,ebx,edi,edi,\
    WS_OVERLAPPEDWINDOW+WS_VISIBLE,esi,esi,esi,esi,ebx,ebx
    invoke SetTimer,eax,ebx,50,ebx;создаем таймер #0 на 50mSec
    mov ebp,esp
; +---------------------------+
; | entering the message loop |
; +---------------------------+
message_loop: invoke GetMessage,ebp,ebx,ebx,ebx
    invoke DispatchMessage,ebp
      jmp message_loop
      ; +----------------------+
      ; | the window procedure |
      ; +----------------------+
window_procedure:
    hWnd     equ ebp+8
    uMsg     equ ebp+0Ch
    expRect  equ ebp - sizeof(RECT)
;собственно прямоугольник, в котором будет происходить вращение текста
    expSize  equ expRect - sizeof(SIZEL)
    enter (sizeof(PAINTSTRUCT) + sizeof(RECT) + sizeof(SIZEL)),0
    mov eax,[uMsg]
    mov edi,[hWnd]
    dec eax; cmp uMsg,WM_DESTROY
    dec eax
    je wmDESTROY
    sub eax,WM_TIMER-WM_DESTROY; cmp uMsg,WM_PAINT
    je   wmTIMER
    sub eax,WM_PAINT-WM_TIMER; cmp uMsg,WM_PAINT
    je   wmPAINT
    leave
    jmp DefWindowProc+exebase
wmTIMER: cmp angle+exebase,3600; сравниваем угол с 360 градусами
    sbb edx,edx;если угол равен 360 делаем угол равным 0
    and angle+exebase,edx
    invoke InvalidateRect,edi,ebx,1; перерисовываем текст с текущим значением угла
    add angle+exebase,16 ; увеличиваем угол на 1,6 градуса
    jmp wmBYE
wmPAINT:;Рисуем строку 225 раз, вращая ее 
; против часовой стрелки на 1,6 градусов 
    invoke BeginPaint,edi,esp
;Создаем Font с углом вращения, указанным в nEscapement
    mov ecx,angle+exebase
    invoke CreateFont,\;создаем новый шрифт 
    26,\;nHeight - желаемая высота символов. Ноль значит использовать 
;pазмеp по умолчанию 
    12,\;nWidth - желаемая ширина символов. Обычно этот параметр равен нулю, что позволяет Windows 
;подобрать ширину соответственно высоте. Однако, в нашем примере, ширина устанавливаемая по умолчанию 
;делает символы нечитабельными, поэтому я установил ширину равную 12
    ecx,\;nEscaрement - указывает ориентацию вывода следующего символа, 
;относительно предыдущего в десятых градусов. Как правило его устанавливают в 0. 
;Установка в 900 вынуждает идти все символы снизу вверх, 1800 - справа налево, 2700 - сверху вниз
    ecx,\;nOrientation - указывает насколько символ должен быть повернут 
;в десятых градусов. 900 - все символы будут "лежать" на спине, и далее по аналогии со следующим параметром.
    400,\;nWeight - устанавливает толщину линии. Windows определяет 
;FW_NORMAL=400
    ebx,\;cItalic - 0 для обычных символов, не нулевое значение приведет 
;к курсивному написанию
    ebx,\;cUnderline - 0 для обычных символов, любое другое значение для
;подчеркнутых символов
    ebx,\;cStrikeOut - 0 для обычных символов, любое другое значение для
;перечеркнутых букв
    -1,\;OEM_CHARSET cCharSet - символьный набор шрифта. Обычно должен быть
;установлен в OEM_CHARSET, который позволяет Windows выбрать системно-зависимый шрифт.
    ebx,\;OUT_DEFAULT_PRECIS cOutрutPrecision - указывает насколько близко
;должен приближаться шрифт к характеристикам, которые мы указали. Обычно этот параметр
;устанавливается в OUT_DEFAULT_PRECIS
    ebx,\;CLIP_DEFAULT_PRECIS cCliрPrecision определяет, что делать с
;символами, которые вылезают за пределы отрисовочного региона
    ebx,\;DEFAULT_QUALITY cQuality - указывает качества вывода, то есть
;насколько внимательно GDI пытаться подогнать атрибуты логического шрифта к атрибутам шрифта
;физического. Есть выбор из трех значений: DEFAULT_QUALITY, PROOF_QUALITY и DRAFT_QUALITY
        DEFAULT_PITCH or FF_SCRIPT,\;cPitchAndFamily - указывает питч и семейство
;шрифта. Вы должны комбинировать значение питча и семейства шрифта с помощью оператора "or".
    offset fontname+exebase,\;"script" lрFacename - указатель на заканчивающуюся
;NULL'ом строку, определяющую гарнитуру фонта
    eax;default font object
    invoke SelectObject,dword ptr [esp+4],eax
;Извлекаем размеры рабочего прямоугольника
    lea esi,[expRect]
    invoke GetClientRect,edi,esi
;---------вывожу текст
    invoke SetTextColor,dword ptr [esp+4],32C8C8h;RGB=50,200,200 золотистые буквы
    invoke SetBkColor,dword ptr [esp+4],0FF0000h;RGB=0,0,255 на синем фоне
    lea eax,[expSize];получаем размеры текста по вертикали и горизонтали
    invoke GetTextExtentPoint,dword ptr [esp+12],offset expTxt+exebase,\
    Num-expTxt,eax
;получаем центр невращаемого текста
    push Num-expTxt ;количество букв в тексте
    push offset expTxt+exebase;адрес строки с текстом
;---------рассчитываю положение начала текста
    finit
db 68h
dd 0.0017453292519943295769236907684886
;   push 0.0017453292519943295769236907684886;=pi/1800 и место для Y-координаты
    push eax;место для X-координаты
    fld dword ptr [esp+4];грузим в FPU коэффициент pi/1800
    mov eax,angle+exebase
    add eax,1800;получили истинный угол
    mov [esp+4],eax
    fimul dword ptr [esp+4];перевели градусы в радианы
    fsincos;в st(0) синус угла, в st(1) косинус
;теперь вычисляем центр вращаемого текста
    mov eax,[expSize+SIZEL.y]
    mul eax ;получили квадрат высоты текста
    mov [esp+4],eax
    mov eax,[expSize+SIZEL.x]
    mul eax ;получили квадрат ширины текста
    add [esp+4],eax;сумма квадратов катетов
    shr dword ptr [esp+4],2
    fild dword ptr [esp+4];квадрат гипотенузы=SQR(SIZE.cy)/4 + SQR(SIZE.cx)/4
    fsqrt
    fistp dword ptr [esp+4];получили гипотенузу
    fimul dword ptr [esp+4];гипотенуза * sin = x
    fistp dword ptr [esp]
    fimul dword ptr [esp+4];гипотенуза * cos = y
    fchs;меняем знак y
    fistp dword ptr [esp+4];-y
    mov eax,[esi+RECT.bottom]
    shr eax,1;y-координата середины экрана
    add [esp+4],eax;y-координата начала текста
    mov ecx,[esi+RECT.right]
    shr ecx,1;x-координата середины экрана
    add [esp],ecx;x-координата начала текста
    invoke TextOut,dword ptr [esp+16];рисуем текст и перемещаем его в заданном квадрате
;--------------------------------------------------------------------- 
    invoke EndPaint,edi,esp
;------------------------------------------------------------------
wmBYE:  leave
    retn 10h
;-------------------------------------------------------------------
wmDESTROY: invoke KillTimer,edi,ebx;уничтожаем таймер #0 
    invoke ExitProcess,ebx;завершаем программу
;данные---------------------------------------------------------------- 
      expTxt    db 'Win32 assembly with MASM is great and easy'
      Num   db 0
      wTitle    db 'Iczelion Tutorial #5-1:Painting with Rotation Text';name of our window
      angle dd 0
      fontname  db "script"
;----------------------------------------------------------------------
import:
dd 0,0,0,user32_dll  ,user32_table
dd 0,0,0,gdi32_dll   ,gdi32_table
dd 0,0,0,kernel32_dll,kernel32_table
dd 0,0
kernel32_table:
ExitProcess             dd _ExitProcess,0
user32_table:
RegisterClass       dd _RegisterClass
CreateWindowEx          dd _CreateWindowEx
GetMessage              dd _GetMessage
DispatchMessage         dd _DispatchMessage
SetTimer        dd _SetTimer
KillTimer       dd _KillTimer
InvalidateRect      dd _InvalidateRect
BeginPaint      dd _BeginPaint
EndPaint        dd _EndPaint
GetClientRect       dd _GetClientRect   
DefWindowProc           dd _DefWindowProc,0
gdi32_table:
CreateFont      dd _CreateFont
SelectObject        dd _SelectObject
SetTextColor        dd _SetTextColor
SetBkColor      dd _SetBkColor
GetTextExtentPoint  dd _GetTextExtentPoint
TextOut         dd _TextOut
CreateSolidBrush    dd _CreateSolidBrush
                        dw 0
_RegisterClass      db 0,0,'RegisterClassA'      
_CreateWindowEx     db 0,0,'CreateWindowExA'
_GetMessage     db 0,0,'GetMessageA'
_DispatchMessage    db 0,0,'DispatchMessageA'
_SetTimer       db 0,0,'SetTimer'
_KillTimer      db 0,0,'KillTimer'
_BeginPaint     db 0,0,'BeginPaint'
_EndPaint       db 0,0,'EndPaint'
_InvalidateRect     db 0,0,'InvalidateRect' 
_GetClientRect      db 0,0,'GetClientRect'
_DefWindowProc      db 0,0,'DefWindowProcA',0
user32_dll      db 'user32'
_ExitProcess        db 0,0,'ExitProcess',0
kernel32_dll        db 'kernel32'
_CreateFont     db 0,0,'CreateFontA'
_SelectObject       db 0,0,'SelectObject'
_SetTextColor       db 0,0,'SetTextColor'
_SetBkColor     db 0,0,'SetBkColor'
_GetTextExtentPoint db 0,0,'GetTextExtentPointA'
_TextOut        db 0,0,'TextOutA'
_CreateSolidBrush   db 0,0,'CreateSolidBrush',0
gdi32_dll       db 'gdi32'
end_import:
end main
_________________________________
© Mikl___ 2013
9
Миниатюры
Сам себе Iczelion  
Вложения
Тип файла: zip tut05a.zip (4.4 Кб, 107 просмотров)
Mikl___
Заблокирован
Автор FAQ
06.01.2013, 04:42  [ТС] #15
Win32 API. Урок 6. Клавиатура
Мы изучим, как Windows программа получает сообщения от клавиатуры.
Скачайте пример здесь
ТЕОРИЯ — МАТЬ СКЛЕРОЗА
Как правило, у каждого компьютера есть только одна клавиатура, поэтому все запущенные Windows программы должны разделять ее между всеми. Windows ответственна за то, чтобы отсылать информацию о нажатых клавишах активному в данный момент окну.
Хотя на экране может быть сразу несколько окон, только одно из них имеет фокус ввода, и только оно может получать сообщения от клавиатуры. Вы можете отличить окно, которое имеет фокус ввода от окна, которое его не имеет, посмотрев на его title bar — он будет подсвечен, в отличии от других.
В действительности, есть два типа сообщений от клавиатуры, зависящих от того, чем вы считаете клавиатуру. Вы можете считать ее набором кнопок. В этом случае, если вы нажмете кнопку, Windows пошлет сообщение WM_KEYDOWN активному окну, уведомляя о нажатии клавиши. Когда вы отпустите клавишу, Windows пошлет сообщение WM_KEYUP. Вы думаете о клавише как о кнопке. Другой взгляд на клавиатуру предполагает, что это устройство ввода символов. Тогда, Windows шлет сообщения WM_KEYDOWN или WM_KEYUP окну, в котором есть фокус ввода, и эти сообщения будут транслированы в сообщение WM_CHAR функцией TranslateMessage. Процедура окна может обрабатывать все три сообщения или только то, в котором оно заинтересованно. Большую часть времени вы можете игнорировать WM_KEYDOWN и WM_KEYUP, так как вызов функции TranslateMessage в цикле обработки сообщений транслирует сообщения WM_KEYDOWN и WM_KEYUP в WM_CHAR. Мы будем опираться именно на это сообщение в данном Уроке.
ПРАКТИКА — СЕСТРА ШИЗОФРЕНИИ
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
.386
   .model flat,stdcall
   option casemap:none
 
   WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
 
   include \masm32\include\windows.inc
   include \masm32\include\user32.inc
   include \masm32\include\kernel32.inc
   include \masm32\include\gdi32.inc
   includelib \masm32\lib\user32.lib
   includelib \masm32\lib\kernel32.lib
   includelib \masm32\lib\gdi32.lib
 
   .data
   ClassName db "SimpleWinClass",0
   AppName  db "Our First Window",0
 
   char WPARAM 20h  ; символ, который программа получает от клавиатуры
 
   .data?
   hInstance HINSTANCE ?
   CommandLine LPSTR ?
 
   .code
   start:       invoke GetModuleHandle, NULL
       mov    hInstance,eax
       invoke GetCommandLine
       mov CommandLine,eax
       invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
       invoke ExitProcess,eax
 
   WinMain proc   hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
       LOCAL wc:WNDCLASSEX
       LOCAL msg:MSG
       LOCAL hwnd:HWND
 
       mov   wc.cbSize,SIZEOF WNDCLASSEX
       mov   wc.style, CS_HREDRAW or CS_VREDRAW
       mov   wc.lpfnWndProc, OFFSET WndProc
       mov   wc.cbClsExtra,NULL
       mov   wc.cbWndExtra,NULL
       push  hInst
       pop   wc.hInstance
       mov   wc.hbrBackground,COLOR_WINDOW+1
       mov   wc.lpszMenuName,NULL
       mov   wc.lpszClassName,OFFSET ClassName
       invoke LoadIcon,NULL,IDI_APPLICATION
       mov   wc.hIcon,eax
       mov   wc.hIconSm,eax
       invoke LoadCursor,NULL,IDC_ARROW
       mov   wc.hCursor,eax
       invoke RegisterClassEx, addr wc
       invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,WS_OVERLAPPEDWINDOW,\
       CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInst,NULL
       mov   hwnd,eax
       invoke ShowWindow, hwnd,SW_SHOWNORMAL
       invoke UpdateWindow, hwnd
       .WHILE TRUE
                   invoke GetMessage, ADDR msg,NULL,0,0
                   .BREAK .IF (!eax)
                   invoke TranslateMessage, ADDR msg
                   invoke DispatchMessage, ADDR msg
           .ENDW
       mov     eax,msg.wParam
       ret
   WinMain endp
   WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
       LOCAL hdc:HDC
       LOCAL ps:PAINTSTRUCT
 
       .IF uMsg==WM_DESTROY
           invoke PostQuitMessage,NULL
       .ELSEIF uMsg==WM_CHAR
           push wParam
           pop  char
           invoke InvalidateRect, hWnd,NULL,TRUE
       .ELSEIF uMsg==WM_PAINT
           invoke BeginPaint,hWnd, ADDR ps
           mov    hdc,eax
           invoke TextOut,hdc,0,0,ADDR char,1
           invoke EndPaint,hWnd, ADDR ps
       .ELSE
           invoke DefWindowProc,hWnd,uMsg,wParam,lParam
           ret
       .ENDIF
       xor    eax,eax
       ret
   WndProc endp
   end start
Разбор полётов
Кликните здесь для просмотра всего текста
Assembler
1
char WPARAM 20h ; символ, который программа получает от клавиатуры
Это переменная, в которой будет сохраняться символ, получаемый от клавиатуры. Так как символ шлется в WPARAM процедуры окна, мы для простоты определяем эту переменную как обладающую типом WPARAM. Начальное значение - 20h или "пробел", так как когда наше окно обновляет свою клиентскую область в первое время, символ еще не введен, поэтому мы делаем так, чтобы отображался пробел.
Кликните здесь для просмотра всего текста
Assembler
1
2
3
4
       .ELSEIF uMsg==WM_CHAR
           push wParam
           pop  char
           invoke InvalidateRect, hWnd,NULL,TRUE
Это было добавлено в процедуру окна для обработк сообщения WM_CHAR. Она всего лишь помещает символ в переменную char и затем вызывает InvalidateRect, что вынуждает Windows послать сообщение WM_PAINT процедуре окна. Синтаксис этой функции следующий:
Кликните здесь для просмотра всего текста
Assembler
1
   InvalidateRect proto hWnd:HWND, lpRect:DWORD, bErase:DWORD
  • lрRect — указатель на прямоугольник в клиентской области, который мы хотим объявить требующим перерисовки. Если этот параметр равен NULL'у, тогда вся клиентская область объявляется такой.
  • bErase — флаг, говорящий Windows, нужно ли уничтожать бэкграунд. Если он равен TRUE, тогда она делает это при вызове функции BeginPaint.
Таким образом, мы будем использовать следующую стратегию:
  1. мы сохраним всю необходимую информацию, относящуюся к отрисовке клиентской области и генерирующую сообщение WM_PAINT, чтобы перерисовать ее. Конечно, код в секции WM_PAINT должен знать заранее, что от него ожидают. Это кажется обходным путем делать дела, но это путь Windows.
  2. Hа самом деле, мы можем отрисовать клиентскую область в ходе обработки сообщения WM_CHAR, между вызовами функций GetDC и ReleaseDC. Hет никаких проблем с этим. Hо вся забава начнется, когда приложению понадобится перерисовать клиентскую область. Так как код, рисующий символ находится в секции WM_CHAR, программа не сможет перерисовать символ в клиентской части. Поэтому помещайте все необходимые данные и код, отвечающий за рисование в WM_PAINT. Вы можете послать это сообщение из любого места вашего кода, где вам нужно перерисовать клиентскую область.
Кликните здесь для просмотра всего текста
Assembler
1
          invoke TextOut,hdc,0,0,ADDR char,1
Когда InvalidateRect вызвана, она шлет сообщение WM_PAINT обратно процедуре окна, поэтому вызывается код в секции WM_PAINT. Он вызывает BeginPaint, чтобы получить хэндл контекста устройства, и затем вызывает TextOut, рисующая наш символ в клиентской области в x=0, y=0. Когда вы запускаете программу и нажимаете любую клавишу, вы увидите, что символьное эхо в верхнем левом углу клиентского окна. И когда окно минимизируется и максимизируется, символ все равно там, так как все код и все данные, необходимые для перерисовки располагаются в секции WM_PAINT.
__________________________________________
© Iczelion, пер. Aquila.
7
Вложения
Тип файла: zip tut06.zip (2.7 Кб, 99 просмотров)
06.01.2013, 04:42
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
06.01.2013, 04:42
Привет! Вот еще темы с ответами:

Запрос сам в себе - Базы данных
Ребята, вот например есть таблица Name Time a 12-04-2011 a 14-04-2011 a 05-04-2011 b...

Комп включается сам по себе - Windows
проблема такая - после выключения компьютера, он самопроизвольно включается каждый раз с разным промежутком времени от 2-3 секунд до 1-2...

Монитор гаснет сам по себе - Мониторы
Купил монитор , подключил его в ноуту. Работают оба монитора вместе (новый и монитор ноута), новый монитор гаснет иногда, всего три раза...

Компьютер сам по себе выключается. - Материнские платы
Комп работает, работат, а потом сам по себе выключается... Иногда долго работает, а инода часто отключается...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
15
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.