Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
выпущен 12 ноября!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

Дневники чайника. Чтива II

Обсудить статью на форуме

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

Автор: Bitfry <bitfry@land.ru>

В поисках приключений на ассемблере под Windows




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


Вступительные мысли


    Здравствуйте, мои читатели! Если вы прочли первую часть и открыли вторую, значит, я не зря мучаю клавиатуру, и я искренне этому рад. Мне все больше и больше нужны читатели, я заболел графоманией и околонаучными изысканиями. Так бывает: был нормальным человеком, потом начал читать, потом писать, потом в компанию плохую попал, так, глядишь, и нет человека, один Bitfry остался :). На самом деле писал я первую часть и думал: "Ай да Bitfry! Ай да :", а как только на свой сайт выложил, посмотрел на это другими глазами. Теперь мне кажется, что плохо получилось: многое непонятно, многое не по теме, и слишком много заблуждений и неточностей. Но бросить я уже не в силах, так что придется писать получше (и думать не только о себе).
    Сайт-то я сделал, как и ожидал, быстро, да только кому этот сайт нужен: тормозной до жути и попасть на него можно, только если я адрес дам (bitfry.narod.ru). Вот, думаю, и прочли-то дневник 3-4 человека. Правда, это дельные люди, один из них Bad_guy - основатель сайта CRACKL@B. Он мне написал письмо, назвал этот документ занимательным и предложил зайти через месяцок запостить статью.
    Обычно в деловых кругах так оформляют вежливый отказ (ОПП - отказ посредством проволочек) и я уж, было, расстроился, но через пару дней Danger по просьбе Bad_guy напомнил мне о возможности публикации после 8 сентября. Так что УРА ТОВАРИЩИ! Ура! Какой хороший парень - Bad_guy =).
    Хотя для вас публикация дневника на крэклабе уже свершенный факт, я жду этого с нетерпением. Постараюсь написать до восьмого три чтивы.
    Форма дневника пока остается, буду больше экспериментировать в отладчике и все, в чем я сомневаюсь, проверять, хотя без глупостей не обойдется. А вы всему не верьте, может, я чего и выдумываю. :)

Ночь шестая. 21 августа 2004 года


    Прошлый урок дался с трудом, осталось много вопросов. Но если учесть, что самые умные гомо сапиенс придумывали это все с тех пор, как появились, то дело идет с космической скоростью.
    Справочник по API-функциям я пока достал вот тут: http://masm.by.ru/books/winapi.zip (русский) и еще на каком-то старом диске взял Win32.hlp английский от Борланда, он более толковый.
    Опять поискал по-быстрому, нет ли толковых учебников по теме. Чего-то как-то плохо ищется. Все, что нахожу, в основном большие учебники, страниц по 700, а в них только 50 стр. посвящены винде, 30 из которых - это код программ, а оставшиеся 20 не так уж интересны.
    Есть несколько уроков на русском, но они умерли, так и не успев начаться.
    В общем, ничего лучше, чем туты от Iczelion'a, найти не смог. Даже на английском Iczelion, похоже, один из лучших практиков. Но он предполагает, что Win Asm - это не первый язык программирования под win32 для читателя и поэтому архитектура форточек описывается поверхностно, как бы в порядке напоминания.
    Для урока 4 "Отрисовка текста" придется взять учебник, в котором достойно описаны принципы "общения" Win32 с программами. Поскольку учебник по архитектуре Windows вне языка программирования - это глупость, а по асму таких учебников нет, придется искать что-нибудь по С++... Нашел много всего: http://www.bcbdev.ru/, и кажется, придется сегодня отвлечься от ассемблера, чтобы 4-й урок стал простым и понятным. Я мог бы добавить эти объяснения в первую часть, но пока это дневник, и к тому же теперь, после прохождения tut3, толкования будут иметь конкретный смысл. Также я могу объяснять все по ходу tut4, но это создаст слишком большие дырки между строками кода, так что лучше я кое-что растолкую сразу, а потом в отладчике подчеркну.

Словарь


    Сообщения Windows. Из термина понятно, что винды в отличие от DOS общаются с программой в обе стороны. Программа вызывает API-функции с параметрами, свойственными данной функции. Винды тоже могут вызывать функцию программы (она же процедура), и тоже с параметрами. В tut3, как и в любом другом приложении с окнами, есть процедура окна, это как раз та функция, которую вызывают форточки.
    Сообщение Windows - это и есть вызов процедуры окна (например, окна tut3), а само сообщение передается через параметры процедуры, они для этого и созданы.
    Если вы думаете, что параметры процедуры окна неизменны или произвольны, вы ошибаетесь, как ошибался я. Параметры процедуры окна зависят от класса окна. На практике все будет видно.
    Язык сообщений не так уж и многословен. В том справочнике, который мне показался почти полным (http://masm.by.ru/books/winmsg.zip), всего 204 сообщения, для полноценного языка маловато. Ну и хорошо! Меньше справочник - меньше в него лазить придется. Для сравнения: API-функций больше тысячи. И учить это все совсем не нужно, нужно научиться пользоваться справкой. Сомневаюсь, что в мире много программистов, которые выучили все Win API и API из DirectX и OpenGL и все новые инструкции в процессорах (MMX, SSE, SSE2, 3DNow, и т.д.) Я вообще сомневаюсь, что те, кто это все в голове держит, могут программировать.
    Хороший программист - не тот, кто все это запомнил, а тот, кто способен быстро и качественно решить поставленную задачу и, самое главное, сформулировать саму задачу. Мысль не моя, но я готов подписаться под этим утверждением кровью :|.

    Message queue (очередь сообщений). При загрузке программы в память форточки строят для нее очередь сообщений. В эту очередь будут поступать сообщения для всех окон. В tut3 мы создали цикл обработки сообщений (message loop), он как раз и выбирал все сообщения из очереди (одно за цикл), а затем переправлял их оконной процедуре (посмотрите исходник tut3 и первую часть).
    Но здесь все, как в жизни: не все сообщения стоят в очереди. :) Некоторые, с мигалками, отправляются непосредственно в оконную процедуру, без всяких пробок.

    GDI (Graphic Device Interface) Графический интерфейс устройства. Чтобы не рисовать программу с нуля и не обращаться к видеокарте с ее графическими режимами, был придуман язык графического программирования, который здорово облегчает создание графики и красивого текста (вы его сейчас читаете). Реализовано все это через те же API.
    Вот такое определение я сочинил после чтения учебников по Си.

    GUI (Graphical User Interface). Графический интерфейс пользователя. Это, собственно, и есть все, что можно увидеть в форточках, кроме, пожалуй, софт айса, его трудно назвать графическим. Хотя как посмотреть, он все-таки и не текстовый в полном смысле слова.

    Invalid rectangle (поврежденный прямоугольник). Это просто. Допустим, вы надвинули на окно tut4 наш Message Box из tut2, а потом передвинули Box дальше, если не обновить экран, получится бяка. Можно обновить все, но эффективней - только тот кусок, по которому проехался Message Box, а кусок этот описывается... догадайтесь как? Правильно, как поврежденный прямоугольник.

    Device context DC (контекст устройства). Мне этот термин не нравится, какой-то он некрасивый, по крайней мере, на русском. Контекст (в общем русском языке) - это ситуация или обстоятельства. В программировании так не скажешь. Да и устройство здесь как таковое - не совсем устройство. Поэтому, что бы я ни написал, все равно неорганично будет.
    Смысл такой: device context - это структура данных, которая содержит параметры, передаваемые устройству вывода, и указатели на выбранные инструменты вывода. Опять же на практике оно понятней.
    Заметка из будущего. 20 сентября я найду у себя в закромах вот такое описание:
    
Давайте определим понятие контекста устройства и контекста отображения. Контекст устройства выступает в роли связующего звена между приложением и драйвером устройства и представляет собой структуру данных размером примерно 800 байт. Эта структура данных содержит информацию о том, как нужно выполнять операции вывода на данном устройстве (цвет и толщину линий, тип системы координат и т.д.).
    Если приложение получает или создает контекст для устройства отображения, такой контекст называется контекстом отображения. Поэтому когда, например, приложение получает контекст для отображения в одном из своих окон, такой контекст называется контекстом отображения. Если же ему требуется выполнять операцию вывода для устройства (для принтера или для экрана дисплея), приложение должно получить или создать контекст устройства. Однако следует понимать, что контексты устройства и отображения содержат описания одних и тех же характеристик и имеют одинаковую структуру. Название контекста определяется только тем, относится ли контекст к окну отображения или устройству вывода.


    Pointer (указатель). Везде по сто раз это слово используется, а определения нет. Вроде как и так понятно, все и так умные. Ничего лучше придумать не могу, как взять определение из компьютерного расширения словаря Lingvo.
    Указатель - ссылка (идентификатор объекта в программе, обычно - адрес этого объекта в памяти)
    Я это дело в отладчике проверю: адрес или не адрес. :)

    Кстати, пока искал определение указателя, наткнулся на описание структуры POINT (помните тот непонятный поинт в структуре msg). Оказывается, эту штуку тоже в MS придумали (структура Windows). POINT-структура определяет X и Y координаты точки.

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

    В умных книжках сокращения обзывают "венгерской нотацией", например: hWnd - это венгерская нотация, handle to a window - описатель окна, (там везде так переводят, а я бы сказал: ручка окна. :)
    Короче, у меня не умная книжка, а просто дневник, и я эту дурь (венгерская нотация) писать не буду, сокращение оно и есть сокращение.

    Вообще, пролистав несколько учебников по Си, могу сказать, что разница между С++ и тем, что Iczelion называет ассемблером, не так уж и велика на этом этапе освоения.
    Теперь я убедился, что изучать асм проще после языка высокого уровня, но проще - не всегда значит лучше, а в свою любимую голову знания нужно "упаковывать" очень аккуратно, она того стоит.

    Прочитал урок 4-й. Слишком коротко показалось, я сейчас это исправлю. :)
    Ну что, поехали потихоньку.

    Собираю build.bat'ом набранный урок 4 (напомню, способ компиляции может изменить EntryPoint, в смысле адрес первого опкода)

build tut4

    Активирую Soft Ice; запускаю tut4; жму Ctrl+D; addr tut4; db 401000; [Un assembly].

Видите, первым делом винды приделывают ручку к tut4.

00401000      PUSH    00                           ; этот параметр - имя модуля, а в конце обязательно 00
00401002      CALL    KERNEL32!GetModuleHandleA    ; модуль это и есть tut4.exe
00401007      MOV     [00403034],EAX               ; сохраняем по адресу 403034 хендл tut4

  

    Командная строка
0040100C      CALL    KERNEL32!GetCommandLineA     ; в этом примере тоже нет обработки командной строки
00401011      MOV     [00403038],EAX               ; но tut4 ее получает и хранит указатель на нее в 403038


    Сейчас узнаю, что здесь за указатель. Смотрю, что в 403038, там у меня в байтах 70 23 14, можно, конечно, и в голове переложить справа налево, но в айсе есть способ лучше:
dd 403038, лезу по этому адресу, там весь путь от диска до tut4.exe в кавычках. Tut4 я, естественно, без параметров запускал, а сейчас попробую из командной строки, с белибердой какой-нибудь типа:
tut4.exe 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF 123456789ABCDEF
Естественно, пробел вместо 0, получается 16х17=272+весь путь. Понятно, что это слишком для командной строки, но мне интересно: сколько в ней символов может быть? Смотрю в айсе. Все как задумано, только я ожидал, что параметры окажутся в кавычках. Выводы следующие: указатель - это точно адрес (и так было понятно, просто захотелось побаловаться :), а командной строки мне хватит, чтобы письма писать :).

Параметры WinMain
00401016      PUSH    0A                           ; SW_SHOWDEFAULT. SW означает Show Window
00401018      PUSH    DWORD PTR [00403038]         ; в айсе я впишу имя "CommandL" для этого смещения
0040101E      PUSH    00                           ; значение переменной hPrevInst всегда 0 (хендл предыдущего экземпляра программы)
00401020      PUSH    DWORD PTR [00403034]         ; в айсе я впишу имя "Handle_tut4" для этого смещения
00401026      CALL    00401031                     ; вызов WinMain


    Выход
0040102B      PUSH    EAX                          ; а сюда вернемся только, чтобы выйти
0040102C      CALL    KERNEL32!ExitProcess         ; выход, наверное, - интересная процедура, но не для меня


WinMain
              Пролог
00401031      PUSH    EBP                          ; стандартный пролог, причем не только для асма
00401032      MOV     EBP,ESP                      ; я посмотрел: во всех (см. сноску 01) приложениях есть то же самое
00401034      ADD     ESP,-50                      ; эта строка может выглядеть иначе (SUB ESP,50) :)


          Регистрация класса окна
                          Загрузка структуры wc  (wc.,)
00401037      MOV     DWORD PTR [EBP-30],00000030              ; размер структуры в байтах
0040103E      MOV     DWORD PTR [EBP-2C],00000003              ; стиль окна
00401045      MOV     DWORD PTR [EBP-28],00401115              ; адрес процедуры окна, помните "сообщения windows"
0040104C      MOV     DWORD PTR [EBP-24],ERNEL32!ExitProcess   ; дополнительные резервные байты (в tut4 они NULL)
00401053      MOV     DWORD PTR [EBP-20],RNEL32!ExitProcess    ; тоже ноль в резерв
0040105A      PUSH    DWORD PTR [EBP+08]                       ; handle модуля (то есть tut4.exe)
0040105D      POP     DWORD PTR [EBP-1C]
00401060      MOV     DWORD PTR [EBP-10],00000005              ; цвет фона (в tut4 будет системный цвет фона)
00401067      MOV     DWORD PTR [EBP-0C],NEL32!ExitProcess     ; хэндл меню (нет меню, значит 0)
0040106E      MOV     DWORD PTR [EBP-08],00403000              ; по этому смещению лежит имя класса окна


    Там строка SimpleWinClass, оканчивающаяся, как положено, нулевым байтом. Она же строка "константа" в исходнике, вписанная в секцию .Data.
    "Константа" в кавычках, потому что в секции .Data[?] описываются переменные, а настоящие константы описаны, например, в includ'ах, но так как значение этой строки задается один раз и до конца, это вроде как и не переменная.
    Пока что я знаю имя только этого класса. Надо достать MSDN и посмотреть, какие бывают еще.

    invoke LoadIcon,NULL,IDI_APPLICATION

    Тут два параметра, суть которых очень проста:
    IDI_APPLICATION в исп. коде пишется так 7F00, почему?
    ID (Identification) - это префикс идентификационных номеров стандартных предметов в виндах, а последняя "[B]I[/B]" в префиксе - иконка. [B]IDI[/B] - ИНН иконки, через несколько строк будет [B]IDC -[/B] ИНН курсора мыши. По-моему, все просто. Ну а что если у нас свои иконки и курсоры? А вот для этого есть следующий параметр.
    NULL - это как всегда не просто 0, а handle модуля, в котором лежит иконка. Если 0 - то стандартная (IDI), а если б иконки в нашем tut4 были, тут был бы его хендл. А еще очень часто иконки в отдельной dll (динамично подключаемой библиотеке), тогда handle dll нужен (dll тоже модуль).
    Да что я все разжевываю, время теряю и страниц нагоняю, как будто мне за них платят :), я все давно понял, а мой читатель не глупее.

00401075      PUSH    00007F00                                 ; [B]ID[/B] стандартной иконки
0040107A      PUSH    00                                       ; handle модуля иконки
0040107C      CALL    USER32!LoadIconA                         ; загружает саму иконку


В прошлый раз я предположил, что иконка загружается в адресное пространство проги. Посмотрим. Ставлю бряк на 40107С, закрываю tut4, открываю снова, в айсе жму F10. В EAX после выполнения LoadIcon, находится 01 00 03. Смотрю, что там в байтах, опять закрываю айс и tut4, открываю снова и правлю один байт по этому смещению (00010003 был 00 стал АА), нажимаю F10 и убеждаюсь, что LoadIcon ни фига не меняет. Значит, 01 00 03  не адрес в tut4, а что-то другое. А в исходнике переменные иконки начинаются с "h" - похоже, это handle.

00401081      MOV     [EBP-18],EAX                   ; теперь этот handle иконки кладем в переменную hIcon
00401084      MOV     [EBP-04],EAX                   ; и его же в hIconSm (Small Icon)
00401087      PUSH    00007F00                       ; [B]IDC[/B]_ARROW. Курсор мыши, который будет в окне
0040108C      PUSH    00                             ; handle модуля, в котором лежит курсор (если 0, то IDC)
0040108E      CALL    USER32!LoadCursorA             ; здесь мы, очевидно, получаем handle курсора
00401093      MOV     [EBP-14],EAX                   ; сохраняем его в hCursor

00401096      LEA     EAX,[EBP-30]                   ; в EAX попадает смещение из EBP минус 30 байт
00401099      PUSH    EAX                            ; кладем в стек "начало" wc (указатель на wc)
0040109A      CALL    USER32!RegisterClassExA        ; если класс зарегистрирован, то в EAX будет не 0


    Я так понял, что функция RegisterClassEx отличается от RegisterClass только тем, что позволяет задавать маленькую иконку (представляю, как рекламщики, менеджеры, художники и программисты RegisterClass расширяли до Ex и сколько это денег стоило).

    Елки-палки! Засиделся, ночь-то кончилась, опаздываю! Дальше - завтра, то есть сегодня ночью.


Ночь седьмая. 24 августа


Ну ладно-ладно, ну поспал немножко, потом дел куча накопилась, подумаешь: завтра стало после-после-после-завтром. Вы-то подряд читаете, а я все равно продолжу.
    Код tut4 остановился на регистрации класса.
    В help'e написано, что API-функция RegisterClassEx вернет ноль в EAX если что-то не так, а если все в порядке, то там будет уникальный идентификатор прошедшего регистрацию класса. Что такое уникальный идентификатор и почему он в tut4 не нужен, потом разберусь, а сейчас - в отладчик.

              Создание окна
0040109F      PUSH    00                             ; lрParam (в уроке3 у Iczelion'a про него подробно написано)
004010A1      PUSH    DWORD PTR [EBP+08]             ; hInst (handle tut4)
004010A4      PUSH    00                             ; hMenu (handle меню окна)
004010A6      PUSH    00                             ; hWndParent (handle родительского окна)
004010A8      PUSH    80000000                       ; nHeight (начальная высота окна в пикселях)
004010AD      PUSH    80000000                       ; nWidth (ширина)
004010B2      PUSH    80000000                       ; Y (нач. координата Y верхнего левого угла окна)
004010B7      PUSH    80000000                       ; X (нач. координата X верхнего левого угла окна)
004010BC      PUSH    00CF0000                       ; dwStyle (стили окна, а подробнее у Iczelion'a)
004010C1      PUSH    0040300F                       ; адрес строки, содержащей имя окна
004010C6      PUSH    00403000                       ; адрес строки, содержащей имя класса
004010CB      PUSH    00                             ; dwExStyle (дополнительные стили окна)
004010CD      CALL    USER32!CreateWindowExA         ; форточки духовно материализуют окно
004010D2      MOV     [EBP-50],EAX                   ; в hwnd закладка хендла окна


    Тут было столько всяких циферок, которые мне хочется покрутить, но этим я займусь позже. Уж больно интересно, что нового по сравнению с tut3.
    Отсюда я начну нумеровать вопросы, эти интересные циферки - вопрос номер 0 (Q000), так я их не потеряю и вам, если захотите ответ сразу получить, искать не придется.

004010D5      PUSH    01                             ; SW_SHOWNORMAL это всего лишь 1
004010D7      PUSH    DWORD PTR [EBP-50]             ; если добавить имя адресу, то рядом с EBP-50 будет имя
004010DA      CALL    USER32!ShowWindow              ; дух, прими форму! И явилось окно
004010DF      PUSH    DWORD PTR [EBP-50]             ; у меня здесь уже DWORD PTR [EBP-50], HandleWin_tut4
004010E2      CALL    USER32!UpdateWindow            ; дух, прими форму и оденься!


    Заметка из будущего: 1 сентября я увижу, что уже здесь нарисуется текст, подробности в будущем.
              Цикл сообщений
004010E7      PUSH    00                        ; wMsgFilterMax (прочел доку, но не понял) (Q001)
004010E9      PUSH    00                        ; wMsgFilterMin (аналогично)
004010EB      PUSH    00                        ; hWnd


    Если hWnd ноль, то GetMessage передаст сообщение куда попало. А попасть можно только в одно окно, так как в tut4 других нету. Если окон много и разница есть, то сюда надо подставлять handle окна. А вообще я уже сказал, что учить все это не нужно. Просто понять интересно.

004010ED      LEA     EAX,[EBP-4C]              ; в двух строках ADDR msg
004010F0      PUSH    EAX      
004010F1      CALL    USER32!GetMessageA        ; тут про потоки хорошо бы узнать, но это не в tut4 (Q002)


    Дальше в исходнике ".BREAK .IF (!eax)" я потом обязательно разберусь: когда этот бейсик появился и в каких дополнениях ".BREAK" описана. (Q003)
004010F6      OR      EAX,EAX      
004010F8      JZ      0040110E                  ; если в EAX был ноль - на выход
004010FA      LEA     EAX,[EBP-4C]      
004010FD      PUSH    EAX      
004010FE      CALL    USER32!TranslateMessage   ; перевод сообщения, если это нажатая клавиша
00401103      LEA     EAX,[EBP-4C]      
00401106      PUSH    EAX                       ; выти из этого цикла может EAX=0 или Dispatch,
00401107      CALL    USER32!DispatchMessageA   ; или GetMessage,
0040110C      JMP     004010E7                  ; или SoftIce, но он все может!

0040110E      MOV     EAX,[EBP-44]              ; в EAX грузится параметр выхода
00401111      LEAVE                             ; эпилог (напомню MOV ESP,EBP и еще POP EBP)
00401112      RET     0010


    Ой! Девочки! Вы сейчас упадете, у одного моего знакомого есть такой:
    Если вы прочли предыдущее предложение и не задались вопросом: "Какого хрена!" или хотя бы не улыбнулись, может, вам лучше отдохнуть?
    Ну не хотите - как хотите. Приступим к самому главному.
    Что-то я замерз, аж трясет, пойду отогреюсь.

Ночь восьмая. 25 августа


Температура 37, насморк, в горле першит, голова гудит, как трансформатор.
    Я полон сил, уверенности, и :
    Больной бред!

Ночь девятая. 27 августа


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

              Процедура окна
    Как многие, наверное, уже заметили, к этой процедуре в коде прямого обращения нет, но это и не нужно. К процедуре окна обращается Windows, а наше дело сообщить ей, где начало этой процедуры. И это уже сделано.
    Вот строка из исходника, в которой происходит объява процедуры окна:
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    Сегодня я уже понимаю, для чего оно нужно. Чтоб форточки (например, посредством Dispatch) посылали сообщения, нужны правила. Напомню из урока 3:
    hWnd - это  хендл окна, которому есть посылка
    uMsg - сама  посылка
    WPARAM и LPARAM - если бандеролью что-то не передать, то это уже посылка получается.

    [B]UINT,[/B] использованный в качестве формы uMsg, - это беззнаковое целое, которое в Win32, естественно, является 32-разрядным, то есть dword.
    Заметка из будущего: сегодня 10 сентября. Я увидел, какой бред мне пришел в голову 27 августа, и здесь не удержался, переписал про беззнаковое целое.
    Ну вот, такая простая вещь и так запутался. Знаковое - это означает, что в переменной будет число + или -. В смысле: положительное значение или отрицательное. Беззнаковое, соответственно, - все биты будут заняты под положительное значение. Читай вопрос (Q008).

    Теперь подождем сообщения.
    Дождались? :) Ну тогда в глубину.
                        Пролог
00401115      PUSH    EBP      
00401116      MOV     EBP,ESP      
00401118      ADD     ESP,-54


    Исходник:
LOCAL hdc:HDC                       = DWORD
LOCAL ps:PAINTSTRUCT      = DWORD+DWORD+RECT+ DWORD+DWORD+BYTE 32 dup
LOCAL rect:RECT                       = dd+dd+dd+dd

Ну надо же, сколько всего я не знаю!
RECT в PAINTSTRUCT - это тот же RECT что и в rect? (Q004)
Что такое dd и почему у Iczelion'a там LONG. Это, как положено, =dword? (Q005)
BYTE 32 dup - это тридцать два раза байт? (Q006)
И вообще, зачем тут что? (Q... номер жалко тратить :)

    А ответы, как в кроссворде: в конце кода.

    Продолжу ковыряться. Пролог был, теперь в исходнике:
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL

    А в исполнении оно так:
0040111B      CMP     DWORD PTR [EBP+0C],02    ; соответственно WM_DESTROY имеет значение 2
0040111F      JNZ     0040112A                 ; 2 - закрыть, не 2  -  следующее если, а точнее, "ещеесли"
00401121      PUSH    00                       ; ну закрыть так закрыть,
00401123      CALL    USER32!PostQuitMessage   ; если дальше неинтересно
00401128      JMP     00401183


    Наконец-то! Вот оно, новое! Исходник:
    .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


    А отладчик:
    Начало рисования
    (получение разрешения в виде хендла контекста устройства и заполнения структуры ps)
0040112A      CMP     DWORD PTR [EBP+0C],0F    ; соответственно WM_PAINT имеет значение 0F
0040112E      JNZ     0040116E                 ; 0F - рисовать, не 0F - отдать концы DefWindowProc
00401130      LEA     EAX,[EBP-44]             ; в EAX загрузить указатель на ps
00401133      PUSH    EAX                      ; указатель ps в стек (ps - неинициализирована)
00401134      PUSH    DWORD PTR [EBP+08]       ; handle окна для рисовки в стек
00401137      CALL    USER32!BeginPaint        ; заполнение ps и получение хендла контекста устройства
0040113C      MOV     [EBP-04],EAX             ; в hdc сохраняем device context handle


    Загрузка структуры rect
0040113F      LEA     EAX,[EBP-54]             ; в EAX адрес структуры rect
00401142      PUSH    EAX                      ; параметр rect pointer в стек (указатель rect в стек)
00401143      PUSH    DWORD PTR [EBP+08]       ; handle того же окна для рисовки в стек
00401146      CALL    USER32!GetClientRect     ; тут я основательно блудил, но это было позавчера (Q007)


    Сама рисовка по средствам DrawText
0040114B      PUSH    25                       ; это значит, что текст будет в центре в одну строку
0040114D      LEA     EAX,[EBP-54]             ; указатель на rect в EAX
00401150      PUSH    EAX
00401151      PUSH    FF                       ; а вот так выглядит -1 в коде (Q008)
00401153      PUSH    0040301F                 ; строка с текстом
00401158      PUSH    DWORD PTR [EBP-04]       ; хендл контекста устройства дисплея
0040115B      CALL    USER32!DrawTextA         ; прямо поверх айса должен появиться текст


    А теперь отдаем концы EndPaint
00401160      LEA     EAX,[EBP-44]             ; ps как параметр для завершения рисовки
00401163      PUSH    EAX                      ; он нужен, чтоб винды знали, что и как рисовалось
00401164      PUSH    DWORD PTR [EBP+08]       ; хендл окна, которое обновилось
00401167      CALL    USER32!EndPaint          ; отдаем Display Device Context handle и сообщаем о конце обработки сообщения WM_PAINT
0040116C      JMP     00401183                 ; перепрыгиваем через DefWindowProc


    DefWindowProc
0040116E      PUSH    DWORD PTR [EBP+14]      
00401171      PUSH    DWORD PTR [EBP+10]      
00401174      PUSH    DWORD PTR [EBP+0C]      
00401177      PUSH    DWORD PTR [EBP+08]      
0040117A      CALL    USER32!DefWindowProcA      
0040117F      LEAVE              
00401180      RET     0010


    Джага-джага (то есть конец)
00401183      XOR     EAX,EAX                  ; чистим EAX
00401185      LEAVE                            ; вернем стек на место (MOV ESP,EBP и еще POP EBP)
00401186      RET     0010                     ; возвращаемся, только куда? (Q009)


    Непонятно, как тут все? Мне тоже, так что я пошел книжки читать. Перекушу как следует и лекарства приму, а вы никуда не уходите.

    Пока Битя кушает, я вам вот что скажу: вы его не обижайте, он у меня мальчик хороший! Правда, ошибок много делает, но я ему помогаю все исправлять. И вы пишите аккуратней, пожалуйста, а то что потом ваши дети о вас подумают?

    Мама! Иди спать! Это мой дневник! Можно мне хоть ночью отдохнуть! ... Задолбали!

    На вопросы буду отвечать долго, и не на все в этой чтиве.

    (Q000) Интересные циферки из "раздела" создания окна
    Я тут подумал и решил, что эти параметры интересней самому потыкать, так что желаю удачи.

    (Q004) RECT в PAINTSTRUCT - это тот же RECT, что и в rect?
    RECT в PAINTSTRUCT - это тоже структура RECT, но в памяти будут две разные переменные.
    Получается в структуре PAINTSTRUCT подструктура RECT (rcPaint), и еще есть отдельная структура rect. Также в структуре PAINTSTRUCT есть переменная hdc, и еще есть та же hdc отдельно.

    (Q005) Что такое dd и почему у Iczelion'a там LONG. Это как положено =dword?
    Теперь dd в windows.inc и [B]LONG[/B] в уроке Iczelion'a. Что такое dd, я знаю еще из уроков Калашникова ([B]D[/B]efine [B]D[/B]ouble word - определить двойное слово), оно же 32-разрядное целое, оно же  dword. Но зачем его LONG'ом обзывать?

    [B]LONG[/B] - 32-разрядное знаковое длинное целое.
    В стародавние времена еще при Win 3.1 были длинные - 32 и недлинные - 16-битные параметры, а в Win32 остались одни названия типа LONG. Чуть выше я описал WPARAM и LPARAM, префикс "W" означает word, а "L" как раз LONG. Это и есть тот случай, когда пережитки шестнадцатеричного прошлого мешают 32-битному настоящему. Сегодня WPARAM=LPARAM=dword. А что будет завтра, даже думать не хочу :).

    (Q006) BYTE 32 dup - это тридцать два раза байт?
    Если сложить все dword, то их 13.
    13*4байта=52d=34h
    В айсе: ADD  ESP,-54.  Значит,
    54h-34h=20h= оставшиеся 32байта

    (Q007) CALL   USER32!GetClientRect

    RECT - это, как несложно догадаться, прямоугольник (rectangle). В файле windows.inc RECT описан как структура windows.

  RECT STRUCT
left         dd      ?      - определение X-координаты для точки левого верхнего угла
top          dd      ?      - определение Y-координаты для точки левого верхнего угла
right        dd      ?      - определение X-координаты для точки правого нижнего угла
bottom       dd      ?      - определение Y-координаты для точки правого нижнего угла
  RECT ENDS

Кто эти названия давал, тот заговор против разума плетет. "Left и top", - я позавчера в больном бреду всю ночь разбирал этот кошмар и еще сегодня полночи убиваю, чтоб переписать понятней.
    На примере GetClientRect все не так, как подписано: не left, не top, не right и не bottom. Я не знаю, где чаще эта структура используется, но как ни крути окно tut4, а поля left и top - всегда нули. Кстати, в help'e (и в других источниках) так и написано: "всегда ноль", но почему так - не написано. Вот на это дело я и положил больную ночь.
    А если серьезно, то эта структура используется в разных целях (то, что подписано про X & Y, не всегда правда). Например, RECT можно использовать для описания прямоугольника на местности, то есть на экране, тогда нужны две диагональные точки или еще что-нибудь как раз на все четыре параметра. А еще ее можно использовать для определения размера, как в данном случае, тогда минимально нужны только два параметра, а другие два могут и не использоваться.
    Вопрос не короткий и не очень понятный с первого раза. Поэтому можно сначала прочесть поверху и понять только вывод, потом в айсе самому покопаться, а если что непонятно будет, тогда прочесть как следует. А можно и не врубаться вообще, я думаю, что оно не очень нужно, но если вы любите, как я или как Буратино, все носом пробовать, то вперед, хуже не будет.

    Что делает GetClientRect и rcPaint из ps?
    По названию понятно (я в доке прочел:), что GetClientRect заполняет структуру RECT размерами клиентской области окна.
    По логике, переменная rcPaint в ps должна заполнять другую структуру RECT размерами того прямоугольника, который нужно зарисовать. Но это неправильная логика. В прошлый раз я выяснил, как оно происходит.
    Долгие бредовые эксперименты показали, что в структуре rect, которую заполняет GetClientRect, поля имеют следующий смысл.

    Значение right задает высоту клиентской области окна (все в пикселях).
    Значение bottom задает ширину клиентской области окна.

    Значение left создает отступ слева. (В примере tut4 это отступы от центровки текста)
    Значение top создает отступ сверху.

    Если интересно как я до этого докапывался, прочтите следующее.

Разбор путаницы с прямоугольниками


    Пора бряки ставить и калькулировать все эти rect'ы и ps'ы.

    В памяти переменные не подписаны, поэтому чтобы быстрее увидеть, какой байт какая переменная, нужно точно понять, как структура ps разложилась в памяти. Если вам это понятно и без объяснений, то вы точно не чайник, сто процентов.
    LOCAL'ы все сложились (hdc+ps+rect) и получили один резерв. Есть только указатели, но этого вполне достаточно. Нужно поставить бряк на строке 401130, тогда в EBP будет то, от чего отнимают 44, чтобы получить адрес первого параметра в ps.
    Но что такое первый параметр, с какой стороны он первый? Я запутался. Это вроде как в стеке должно быть, а если нет? Тут точно знать нужно.
    Точно я знаю, что в ps 8dword+32byte.
    Теперь: с EBP-44 по EBP-54 получается 54h-44h=10h=16byte=4dword
    Я не понимаю, что в 4 d-слова может влезть из ps-структуры. С одного конца до RECT только 2dword, а с другого - 32byte+2dword. Прочту как следует, что такое PAINTSTRUCT в доке от Борланда. Перевожу:

    Члены:
    hdc
    Определяет хендл контекст устройства дисплея, который будет использоваться для рисования.
    fErase
    Определяет, нужно ли стирать background (фон). Это значение - не ноль, если прога должна стереть background. Прога отвечает за очистку фона, если класс окна создан без background brush (кисти фона).  :
    rcPaint
    Определяет RECT структуру, которая описывает верхний левый и нижний правый углы прямоугольника, заявленного для рисования.
    fRestore
    Резерв (используется внутренне Windows)
    fIncUpdate
    Резерв (используется внутренне Windows)
    rgbReserved
    Резерв (используется внутренне Windows)

    Дока, конечно, замечательная, мол, типа программисту больше знать не нужно, слишком умный будет.
    Еще раз по-другому: rcPaint содержит координаты прямоугольной области обновления.
    Получается, что в ps есть и hdc и rect, и еще они есть отдельно как самостоятельные переменные. После выполнения BeginPaint один hdc заполняется в ps, а другой hdc возвращается в EAX.
    Как разобраться быстро, я не придумал. Попробую так: после бряка на 4014137, по смещению EBP-44 забиваю FF, много FF, только не слишком много, а то там чуть ниже адреса идут. Так если их стереть, то комп повиснет (пробовал :). И чтоб удобней было в режиме dword (dd EBP-44).
    Выполняю строку BeginPaint без захода в функцию. Смотрю, заполнились 16 dword, некоторые - нулями, сейчас я вам покажу:

   02010705        00000000        00000000        00000000
   000004A8        00000331        00000000        00000000
   AA3D3CB8        AA3D3CBC        00000000        00000000
   AAC31854        A9E7B1B8        80042000        A9E7B207

Вот примерно так выглядит участок памяти от EBP-44 до EBP-4
    Следующая за ps переменная - hdc (EBP-4), это понятно из кода (40113С). Теперь можно сопоставить. Выполняю еще одну строку, в которой переменная hdc (EBP-4) получает значение из EAX.  И ура, я вижу, что hdc (EBP-4) = hdc (EBP-44). Ура потому, что теперь я знаю: переменные уложены в том же порядке, что и в описании структуры.
    Теперь условно подпишу:

     hdc            fErase      rcPaint (left)       top
   02010705        00000000        00000000        00000000
     right          bottоm         fRestore       fIncUpdate
   000004A8        00000331        00000000        00000000

  rgbReserved    rgbReserved     rgbReserved     rgbReserved
   AA3D3CB8        AA3D3CBC        00000000        00000000
  rgbReserved    rgbReserved     rgbReserved     rgbReserved
   AAC31854        A9E7B1B8        80042000        A9E7B207

      hdc                    
   02010705                    

    Самое интересное - это прямоугольник из четырех dслов, я начал разбор как раз для того, чтобы выяснить, чем этот прямоугольник заполнится. Напомню: left и top почему-то всегда 0.
    Поменял right и bottom в переменной rcPaint, ничего не изменилось. Похоже, DrawText вообще не использует структуру ps. Отложу ее.
    Теперь, зная порядок в памяти, посмотрю второй RECT (EBP-54), который заполняет GetClientRect.
    Там точно те же параметры цифра в цифру, но если их изменить, результат виден на экране после отрисовки текста. Текст уходит в центр right и bottom.
    В help'e про GetClientRect написано: поля left и top всегда ноль, а right и bottom содержат высоту и ширину окна. То есть высота и ширина клиентской области окна. Но еще важно знать положение этого прямоугольника на экране. Для этого где-то должны быть еще два параметра, типа X и Y точки, определяющей верхний левый угол (как при создании окна).
    Например, открываю окно tut4, срабатывает бряк, в айсе выполняю GetClientRect и после этого в поля right & bottom вписываю 111 и 111, рисую текст (DrawText) и вижу его не в центре, а выше и левее. А теперь то же самое с окном, сдвинутым от центра вниз и вправо, те же 111 и 111, а текст не там же (соответственно ниже и правее).
    Позавчера в моей голове примерно здесь четко прямоугольники начали выпирать, и мне кажется, что дело тут не только в простуде.
    На счет координат угла - это понятно, где-то они есть, ну и бог с ними. А вот зачем left & top нужны? Они же не для красоты вставлены.
    Поменял значения раз 20, и все стало понятно.

    Вот сейчас сижу, потягиваю кипяченую родниковую воду с медом и лимоном (это вместо чая от простуды хорошо помогает), и мне кажется все это простым и понятным. А в первый раз я думал, что это не понять без суперучебника и долгой практики. На самом деле все действительно просто.
    Функция DrawText с теми параметрами, которые заданы в tut4, рисует текст в центре клиентской области, но сама она не умеет определять этот прямоугольник (или квадрат:). Для этого нужна GetClientRect, она умеет определить размер, а еще в структуре rect (в этом случае) предусмотрены отступы сверху и снизу от этого размера, вроде как поля на листе бумаги.

    (Q008) К вопросу об отрицательных числах (почему -1 выглядит как FF)
    Сегодня 10 сентября, здесь бреда было еще больше, ну не мог я не поправить.

    Надо заметить, что в этом вопросе я буду писать - переменная, но это будет означать знаковое целое. И еще: регистр будет означать бит в двоичной системе (мне так проще =).

    На самом деле это проходят в школе на уроках информатики, но я в школе не учился, а образование у меня гуманитарное. Мое мнение такое: если вы не программист и не имеете технического образования, это не значит, что вы не можете стать исследователем. Главное - желание (чтобы в голове чесалось: как оно работает ;).
    Как оно может работать?
    Я бы сделал так. В памяти нет знака "-", но есть очень много всяких битов :). В каждой переменной назначил бы один бит вместо минуса (1="-" и 0="+"). А если мне не нужны отрицательные числа? Нет, так не пойдет. Можно выделить специальный бит-флаг в процессоре для этой же цели. Но это тоже не универсальный выход. Нужно сделать и флаг, и только положительные переменные, и знаковые переменные.
    А сегодня (10.09) я узнал про допкод - дополнительный способ кодирования чисел со знаком. Тут битовая логика. Я об этом прочитал в этих уроках: http://www.taspro.narod.ru/doc/assembler/uroks.zip (если ссылка не работает, ищите в поиске "Win32Asm Tutorial by Exagone русская версия"). Потом спросил у друга, потом переварил и вот пишу, как все это понимаю.
    В допкоде назначен не просто бит для знака минус, а старший бит.
    Возьмем двойное слово, в нем может быть от
00000000000000000000000000000000 в bin до
11111111111111111111111111111111

    Если нам нужна знаковая (то есть "+-") переменная, то выделенный бит будет знаком.
    После включения этого бита значения идут зеркально (от большего к меньшему). Теперь посмотрим в hex виде.
    В той же переменной типа dword, если включить все биты, кроме старшего, получится 7FFFFFFF в hex. Следующее число будет отрицательным. От 80000000h до FFFFFFFFh располагается отрицательный ряд чисел.
    Примерно так:

-1h                =      FF FF FF FFh
-2h                =      FF FF FF FEh
-3h                =      FF FF FF FDh
......              
-7F FF FF FEh      =      80 00 00  02h
-7F FF FF FFh      =      80 00 00  01h
-80 00 00 00h      =      80 00 00  00h


    Представьте себе любой счетчик, например электро- или километровый. Вот в этом счетчике 8 разрядов (в hex виде), и вы все заполнили FFFFFFFF, следующее число будет 00000000, а теперь отнимите от него одно значение, и вы увидите на циферблате опять F'ы, и так до середины.
    Если вы еще не поняли, не переживайте. Я плохо объясняю, нужна тренировка на задачах и примерах.
    Я, честно сказать, пока еще не понял, для чего это нужно, но мне кажется, что допкод упрощает вычисления (по крайней мере, в тех операторах, которые я знаю).
    Например, возьму "минус-байт" и сделаю его положительным. Для наглядности в исходник tut2 вставлю эти строки:
push     AX               ; сохраню AX
mov      AL,0F9h          ; в регистр AL грузим число -7 (в допкоде = F9h)
not      AL               ; устанавливаем обратное значение битов (0 в 1, 1 в 0), ну инвертируем, короче
add      AL,1             ; Не понял, что прибавляем?
pop      AX               ; AX на место


    Собираю tut2a и в айсе вижу:
MOV      AL,0F9h          ; AL = F9h
NOT      AL               ; AL = 06
ADD      AL,1             ; AL = 07, вот и положительная семерка


    Зачем нужно прибавлять единицу?
    Ага, понял. В одном байте может быть число до 255 в десятичном виде - это нечет. Но значений (в смысле символов) 256 - а это чёт. Ноль - это тоже символ! Вот символ нуля и прибавляется для четности. Может, это и глупость.
    Хорошо бы учебник толковый почитать.
    Съездил, купил учебник - дурной, но дешевый (и единственный в магазине), почитал, подумал.
    Не так. Если все делать по-моему (назначить один бит для знака), то этот регистр пропадает. Это не эффективно, особенно в простеньких микросхемах, в которых все основы строились. А если допкод ввести, то этот регистр используется и в минус числе допкода, и в пересчете на положительное значение.
    Старший бит в допкоде - и знак, и число. Смотрите, что получается:
    возьмем самое большое отрицательное число в байте 10 00 00 00 = 80h=128d
    и сделаем его положительным (обратите внимание на двоичный код)
MOV      AL,80h           ; AL = 80h = 128d  = 10 00 00 00
NOT      AL               ; AL = 7F     =      01 11 11 11
ADD      AL,1             ; AL = 80h    =      10 00 00 00


    Если просто перевернуть биты (NOT), то старший регистр при дальнейших вычислениях бездействует, а если прибавить единицу, то можно использовать все 8 бит.
    Вот таким хитрым способом мы экономим один бит.
    Но самое интересное, что процессору неудобно вычитать, а удобно складывать. И тут дополнительное представление гораздо важнее. Это я прочел у Питера Абеля в древнем, но хорошем учебнике (он в сети везде есть). Почитайте главу "Отрицательные числа". Я приведу только один пример:
    65               01000001
+(-42)             11010110
-----                ------------
  23      (1)       00010111

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

    "Интересное" определение В.И. Юров дает:
    "Как представляются в компьютере числа со знаком?
    Положительные целые числа со знаком - это 0 и все положительные числа.
    Отрицательные числа со знаком - это все числа меньше нуля".
    Я в математике не силен (я ею просто не занимался :) но, по-моему, ноль нельзя считать положительным или отрицательным.
    Теперь все-таки предположим, что ноль - в положительном ряду, получается нарушение симметрии. А для восстановления этой самой симметрии можно к отрицательному ряду в вычислениях прибавлять регистр (add AL,1). Появляется хитрая возможность использовать все регистры в переменной.

    Подведу итоги.
    Отрицательные числа представляются в допкоде.
    Положительные - в двоичном (прямом) коде.
    Чтоб узнать (в hex виде), в каком диапазоне: "+" или "-" находится значение, нужно взять максимальное количество символов в переменной и разделить на два. Полученное значение будет первое отрицательное (оно же старшее). Если значение "больше либо равно", значит "-", если "меньше", значит "+".
    Или перевести в двоичную систему и посмотреть, включен ли старший бит.
    А по-русски: младшая половина значений - положительная, а старшая - отрицательная (но зеркально).
    Еще раз повторяю: это битовая логика и суть именно в двоичной системе.
    Я это переваривал с карандашом и бумагой (в столбик, как в школе :).

    Теперь вернусь к вопросу =). Почему -1 (в tut4) выглядит как FF, хотя должно быть dword.
    В исполняемом коде tut4 в этом случае будет 6АFF, то есть запишется только один байт из всего dword. Видимо, push в 32-битных прогах соображает на 32 бита и в стек кладет dword.
    Если ввести отрицательное число, которое не влезет в байт, например -1234, то компилятору придется вставить в код все четыре байта (dword) FF FF FB 2E.

Ночь десятая. 29 августа



    Сегодня опять поискал учебники и нашел полезные уроки по асму: http://learnasm.narod.ru/, они "более русские", и, кроме того, есть некоторые толкования для начинающих, жаль что мало (и уроков, и толкований). На этом же сайте вы можете взять Win32 Help. Я буду читать их параллельно с Iczelion'овскими. И обязательно буду вписывать в дневник, что узнал из этих уроков.

    Из вышеупомянутого источника я узнал, что ret 0010 делает возврат по адресу, взятому с вершины стека, а потом выбирает из стека 16 байт (10h=16d=4dw). Получается - ret, умница, выравнивает стек. Но откуда здесь эти 4 dслова? Это можно понять из кода tut4.
    В проге всего 3 раза используется команда ret, и все три со значением 10h.
    В первом случае это выход из программы в цикле сообщений.
    Второй и третий - это уход куда-то (пока не знаю куда) из процедуры окна.
    Что объединяет эти случаи? А вот что:
      LEAVE             ; восстановление стека = MOV ESP,EBP  и  POP EBP
      RET    0010


    Получается, что неизвестные 4 dword уложены в стек до пролога. Поняли? Если да, то начнем копать всю tut4 с самого начала.
    Случай с WndProc сложнее, поэтому в качестве разбора возьму первый ret (из цикла сообщений).
    До пролога в этом случае мы имеем только начало программы:

    Получаем handle tut4
00401000      PUSH    00                         ; этот параметр - имя модуля, а в конце обязательно 00
00401002      CALL    KERNEL32!GetModuleHandleA  ; модуль - это и есть tut4.exe
00401007      MOV     [00403034],EAX             ; сохраняем по адресу 403034 хендл tut4


    Командная строка
0040100C      CALL    KERNEL32!GetCommandLineA   ; в этом примере тоже нет обработки командной строки
00401011      MOV     [00403038],EAX             ; но tut4 ее получает и хранит указатель на нее в 403038


Параметры WinMain
00401016      PUSH    0A                         ; SW_SHOWDEFAULT. SW означает Show Window
00401018      PUSH    DWORD PTR [00403038]       ; в айсе я впишу имя "CommandL" для этого смещения
0040101E      PUSH    00                         ; значение переменной hPrevInst всегда 0 (хендл предыдущего экземпляра программы)
00401020      PUSH    DWORD PTR [00403034]       ; в айсе я впишу имя "Handle_tut4" для этого смещения
00401026      CALL    00401031                   ; вызов WinMain


    Выход
0040102B      PUSH    EAX                        ; а сюда вернемся только чтобы выйти
0040102C      CALL    KERNEL32!ExitProcess       ; выход - наверное, интересная процедура, но не для меня

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

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

00401031      PUSH    EBP                        ; Сохраняем в стек значение EBP
00401032      MOV     EBP,ESP                    ; Сохраняем вершину стека в EBP
00401034      ADD     ESP,-50                    ; Создаем фрагмент стека размером 80 байт


    Все функции API сами чистят свои параметры, об этом в исходнике tut4 говорит следующая строка.
.MODEL FLAT, STDCALL
STDCALL значит, что параметры будут от последнего к первому, и функция сама выравнивает стек (это первый урок Iczelion'a).

    Остаются только параметры WinMain. Не верю! Поищу от WinMain до ret, нет ли где выборки из стека этих параметров.

Сначала в стек толкаем 4 dword (параметры WinMain) и вызываем саму WinMain
            invoke WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT


В первой чтиве я написал, что объява WinMain в полезном коде не участвует, но я так и не понял, что она делает, а вот сейчас дошло.
WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
Эта строка связывает параметры в стеке (те самые, которые передаются WinMain) с определенными именами.
То есть, как мне кажется, в стеке теперь с точки зрения процедуры WinMain не просто цифры, а переменные с определенными именами и заданным размером. Это важно!

Потом создаем фрагмент стека (уводим вершину стека на 80 байт выше)
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND


Заполняем созданный фрагмент стека структурой wc.
    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+0
    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

Только сейчас увидел! Есть отличие от предыдущего урока, в исходнике tut4:
    push  hInst
а в tut3:
    push  hInstance
Оба варианта работают одинаково, но суть разная!
    Не успеваю я решить один вопрос, как на меня сваливаются еще два. Сейчас буду разбираться.
    В айсе тоже есть заметная разница:
0040105A      PUSH   DWORD PTR [EBP+08]    ; handle модуля tut4 берется из параметра переданного WinMain
0040105D      POP    DWORD PTR [EBP-1C]


    а это из tut3
       push     DWORD PTR [00402020]       ; handle модуля tut3 берется из переменной hInstance
       pop      DWORD PTR [EBP-1С]


    В tut3 хендл модуля загружается в стек из объявленной в секции .DATA? переменной hInstance.
    А в tut4 происходит обращение к одному из 4 параметров WinMain переменной hInst, она не объявлена в секциях .DATA или .DATA?, она локальная, еще раз напомню: локальная - значит внутрипроцедурная.

    Раньше я и предположить не мог, что со стеком такие фокусы выделывают.
    Командой push по справочнику в стек что-то укладывают, а здесь в Win32 получается, что ею как бы из стека даже вынимают. К этому надо привыкать и еще много раз объяснять самому себе, что такое фрагмент стека и как с ним обращаться. А можно просто писать на C++ и не париться :).
    Я-то точно знаю, что мне нужен ассемблер, и вы, думаю, тоже. Потому что досюда дочитали не все, а только те, кто точно знает, чего хочет, а из них те, у кого голова светлая и соображучая. Нет, конечно, есть и те, кто поверху пробежал, ничего не понял, а исследовать хочется, вот и читают. Таким читателям я тоже рад, потому что придет время, и они будут знать, где ответы искать, и еще не раз вспомнят то, что было так непонятно. Я это по себе знаю.
    Здорово я отвлекся, даже начал нравоучать. Это потому, что я устал и проголодался, пойду есть и медитировать, у меня после еды это само как-то получается.

    Мне кажется, что все уже забыли, что я ищу. А ищу я 4 двойных слова из стека, эти слова ret 0010 вынимает, чтобы выровнять стек, и я их вроде бы нашел. Но для уверенности проверяю, они это или нет. В tut4 уже выполнилось много строк, а дальше я писать не буду, и так лишнего накатал.
    Суть в следующем: что бы ни происходило внутри процедуры, если регистр EBP не меняется (а это норма), то эпилог возвращает значение и EBP, и ESP. Поэтому после команды leave в стеке окажется то, что было до пролога, а там были те самые 4 параметра, которые мы сообщили WinMain. Вот ret и избавляется от старых данных, уже отработавших WinMain.
    Теперь оно же в WndProc. Прямого вызова в tut4 нет, и параметры в стек вроде не кладутся. А вот и кладутся! Обязательно кладутся!
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

Это объява процедуры окна. Она описывает те 16 байт, которые сейчас сверху лежат в стеке как переменные. Значит, тот, кто вызвал, положил эти параметры, а если не положил, сам дурак, и прога зависла. :)

    (Q009)  Строка 401186 команда RET 0010, куда возвращаемся из WndProc?

    А действительно, куда прога должна вернуться из процедуры окна, если она явно к ней не обращалась. В tut4 никакой Call в стек ничего не положил, и возвращаться вроде бы некуда.
    Нет, логически понятно, что после обработки сообщения, например WM_PAINT, нужно вернуться в цикл сообщений за следующим. Но как это происходит?
    В айсе выполняю ret и ухожу в user32. Что происходит? Ret берет из стека адрес возврата. В стеке после leave, естественно, будет то, что положил вызывающий (в этом случае вызывающий была одна из API user32.dll).
    Стоп, чувствую я, что дело это долгое. Я и так сеночью потратил 4 часа на эту ret 0010.
    Вопрос четкий, попробую в инете найти готовый ответ: Не нашел, теперь попробую вопрос задать на форуме, а вот если не ответят за пару дней, тогда сам буду разбираться.

    Подведу итоги: опять осталась куча вопросов. Я не ответил на:
    (Q001) Про фильтры wMsgFilterMax и wMsgFilterMin из цикла сообщений.
    (Q002) Про потоки, но это не вопрос, а целая история.
    (Q003) Про Директивы MASM'a, похожие на бейсик.
    Ну и на девятый вопрос жду ответа с форума.

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

День одиннадцатый и двенадцатый. 30,31 августа


    В связи с наступлением осени перехожу на дневное расписание, "отпуск" кончился, дел по горло. Днем столько не напишешь, биополя мешают, вертятся вокруг. Да и вредно по 10 часов за компом сидеть, надо с живыми людьми общаться, будь они не ладны. В общем, нет у меня больше возможности так писать.
    Теперь я буду в дневнике несколько дней склеивать, но результат будет не хуже, а может быть, даже и лучше, так как на девятом часу исследований в голове каша. А за 2-3 часа и устать не успеешь.
    В форуме я вопрос неправильно задал, и дискуссия пошла не в том направлении. Но потом все встало на свои места, и мне посоветовали почитать умную книжку - Джеффри РИХТЕР "Создание эффективных WIN32-приложений с учетом специфики 64-разрядной версии Windows", если вы читали мой дневник с интересом, то эта книга у вас уже есть. :)
    Для того, чтобы разобраться с вопросом (Q009), нужно прочесть главу 26  "Оконные сообщения".
    В этой главе каждое второе слово "поток", а я не знаю, что это такое, придется сначала про эти вездесущие потоки читать.
    Слушайте, какая хорошая книжка! Вот дурень, я эту книгу отложил из-за названия, оно мне показалось слишком умным и не по теме. Урок мне на будущее: сначала прочти предисловие, а потом решай - хорошая книга или нет. Если б я ее раньше почитал, столько б дней сэкономил.

    Читал, думал. Понял, что в этой книге грамотного ответа на вопрос (Q009) нет. Есть только намек (см. в конце текста).
    Сам я так и не додумался, что означает первый пункт в этом перечислении, тут меня друг выручил. Но если подумать, то это не так сложно.
    Итак, отработала процедура окна, строка ret 10 посылает в user32. Если есть еще сообщение, то после колдовства виндов управление вернется к tut4 в её цикл сообщений (см. сноску 02). А если сообщений нет, на фига тогда цикл крутить, проц занимать? Вот поэтому возврат происходит не сразу из user32 в цикл сообщений, а через кучу всяких функций. Можно предположить, что данное толкование не корректно, не полно и, может быть, даже не о том.
    Как выразился один из участников  форума: "Сообщения виндоса - штука довольно муторная". Поэтому вопрос остается открытым (тема обсуждается).

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

    Бросать эту писанину я не собираюсь, так что можете смело писать мне гневные письма типа: да как ты смеешь людей обманывать! Ты пишешь, что ... а в любом учебнике написано, что оно так... Прочитай сначала основы в учебнике ... , а потом пиши с умным видом.
    Примерно такой реакции от знающих людей я и жду (ну может быть, чуть повежливее ;)
    C мыслями и замечаниями обращайся к Битфрайу на ящик bitfry@land.ru



Сноски:
01. Разные компиляторы по-разному оптимизируют программу, но суть остается. В большинстве программ используется фрагмент стека (стековый фрейм).

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





Джеффри РИХТЕР "Создание эффективных WIN32-приложений с учетом специфики 64-разрядной версии Windows"
    Цитата из главы 26

    Алгоритм выборки сообщений из очереди потока
    Когда поток вызывает GetMessage или PeekMessage, система проверяет флаги состояния очередей потока и определяет, какое сообщение надо обработать (рис 26-2)
    1. Если флаг QS_SENDMESSAGE установлен, система отправляет сообщение соответствующей оконной процедуре. GetMessage и PeekMessage контролируют процесс обработки и не передают управление потоку сразу после того, как оконная процедура обработает сообщение, вместо этого обе функции ждут следующего сообщения.

    2. Если очередь асинхронных сообщений потока не пуста, GetMessage и Peek Message заполняют переданную им структуру MSG и возвращают управление в Цикл выборки сообщений (расположенный в потоке) в этот момент обычно обращается к DispatchMessage, чтобы соответствующая оконная процедура обработала сообщение.

    3. Если флаг QS_QUIT установлен, GetMessage и PeekMessage возвращают сообщение WM__QUIT (параметр wParam которого содержит указанный код заверше ния) и сбрасывают этот флаг.

    4. Если в очереди виртуального ввода потока есть какие-то сообщения, GetMessage и PeekMessage возвращают сообщение, связанное с аппаратным вводом.

    5. Если флаг QS_PAINT установлен, GetMessage и PeekMessage возвращают сооб щение WM_PAINT для соответствующего окна

    6 Если флаг QS_TIMER установлен, GetMessage и PeekMessage возвращают сообщение WM_TIMER.

От автора: Для полного понимания также прочитайте Дневники чайника. Чтива 0, виток 0 + исправления.



Обсуждение статьи: Дневники чайника. Чтива II >>>


Комментарии к статье: Дневники чайника. Чтива II

Автор 15.08.2005 05:06:17
Чтива 0 виток0 и даже одна глава из витка1 готовы.
Читать всем новичкам обязательно!
Дневники I,II,III скорее всего будут подправлены. Здесь довольно много лишнего и слишком много ошибок.
Поэтому и читайте Чтиву0.

---

Материалы находятся на сайте https://exelab.ru



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS