Ассемблер для начинающих. Дневники чайника. Чтива II

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


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

Автор: 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, и все стало понятно.

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

Обсуждение статьи: Ассемблер для начинающих. Дневники чайника. Чтива II >>>


Комментарии к статье: Ассемблер для начинающих. Дневники чайника. Чтива II

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

---

При перепечатке ссылка на https://exelab.ru обязательна.



Видеокурс ВЗЛОМ