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

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


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

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

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

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

Автор: Bitfry

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


    Только что закончил статью, поэтому глупостей в ней пока не заметил, помогите мне их обнаружить!

1 сентября


    Про мои глупости из прошлых чтив
    В первой чтиве (14 августа) я путал и мудрил насчет прототипов и структур.
    Прототип есть у функций, а у структур - описание (состав, свойство).
    За пояснение большое спасибо Zoo.
    На счет пролога - во второй чтиве я писал, что все программы имеют примерно такой код:

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

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

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

    Про цвет
    Я на этом деле @ съел. В любой палитре в форточках визуально можно понять, что такое RGB. И не надо цифры смешивать, это гораздо проще, чем пишет Iczelion.
    Вы, наверное, знаете, что почти все оттенки, которые могут отобразить современные мониторы и воспринять большинство людей, умещаются в 24 бита (3 байта), это довольно большое значение - 16 777 216 цветов (цифра получается простым перемножением 3 байт RGB цвета).
    Четвертый байт появился для выравнивания до 32 бит. Но это не значит, что он не используется. Например, 4-й байт позволяет упростить создание прозрачности (альфа-канал). Раньше для получения туманной тени требовались длинный код программы и большие ресурсы процессора. Теперь вместо проца большую часть расчетов делает видеокарта, и степень прозрачности выставляется в одном байте, а все нужные функции уже есть в DirectX, в OpenGL, в драйверах видяхи и в самих виндах. Так, в XP на рабочем столе под каждой подписью иконки можно включить полупрозрачную тень.
    Правда к теме дневника это все отношения не имеет, знания простого юзера, так сказать. Может быть, четвертый байт чаще используется в других целях.

    Начну сегодня с исходника. С первого взгляда видно, что прибавилась еще одна библиотека gdi32.dll (загляни в словарь в начале чтивы II). Очевидно, что новые API будут в этой библиотеке.
    include masm32includegdi32.inc
...
    includelib masm32libgdi32.lib
Также появилась новая интересная переменная:
    FontName db "script",0

    Кстати, я заметил, что от tut2 до tut5 exe'шник растет в основном за счет строковых переменных и новых API. Оно и понятно, уроки-то - про API функции, с минимумом логики в самом коде.

    Теперь суть дела: собираю tut5 и ухожу в айс. Опытным глазом исследователя :) я сразу же вижу начало процедуры окна.
WndProc
      Пролог
00401115    PUSH    EBP                      ; сохраняем одно в другое
00401116    MOV     EBP,ESP                  ; другое в одно
00401118    ADD     ESP,-48                  ; и все это подальше от дурных воздействий

0040111B    CMP     DWORD PTR [EBP+0C],02    ; соответственно в EBP+0C находится uMsg
0040111F    JNZ     0040112D                 ; если 2, то выход
00401121    PUSH    00                       ; а перед выходом нужно послать сообщение WM_QUIT,
00401123    CALL    USER32!PostQuitMessage   ; чтоб прервать цикл сообщений
00401128    JMP     004011E5                 ; вот теперь выход

0040112D    CMP     DWORD PTR [EBP+0C],0F    ; если uMsg - 0F (то есть WM_PAINT), то начнем урок,
00401131    JNZ     004011D0                 ; а если нет, то на DefWindowProc

    Урок 5 - рисовка текста с отвратными цветами и шрифтом :)
00401137    LEA     EAX,[EBP-44]             ; указатель на ps
0040113A    PUSH    EAX                      ; для последующего заполнения
0040113B    PUSH    DWORD PTR [EBP+08]       ; handle to a window как параметр
0040113E    CALL    USER32!BeginPaint        ; получение пропуска
00401143    MOV     [EBP-04],EAX             ; получается, что в EBP-4 локальная переменная hdc

    invoke CreateFont
00401146    PUSH    00403042                 ; в 403042 новая переменная (имя шрифта)
0040114B    PUSH    40                       ; DEFAULT_PITCH or FF_SCRIPT - интересно,
                                             ; как это складывается? (Q00A)
0040114D    PUSH    00                       ; DEFAULT_QUALITY Нет, ну тут я точно переделаю
0040114F    PUSH    00                       ; CLIP_DEFAULT_PRECIS меня всю жизнь интересовало,
                                             ; как  форточки с широкими и высокими буквами
                                             ; работают, а сейчас не до этого :)
00401151    PUSH    00                       ; (Precision - точность, безошибочность)
00401153    PUSH    000000FF                 ; OEM_CHARSET параметр символьного набора шрифта
00401158    PUSH    00                       ; не перечеркнутый
0040115A    PUSH    00                       ; не подчеркнутый
0040115C    PUSH    00                       ; не косой
0040115E    PUSH    00000190                 ; толщина линии (маловато будет :)
00401163    PUSH    00                       ; команды ложись не было
00401165    PUSH    00                       ; слева направо равняйсь!
00401167    PUSH    10                       ; окно такое большое, а буквы такие узкие. Я "AA" забабахаю,
00401169    PUSH    18                       ; а высоту "FF" поставлю
0040116B    CALL    GDI32!CreateFontA        ; создать логический шрифт

    Никогда раньше и не задумывался, что один и тот же шрифт с разными параметрами - это для программы разные логические шрифты. Это ж сколько их у меня сейчас на экране, это ж сумасшедшая сумма получается :0
    invoke SelectObject
00401170    PUSH    EAX                      ; там теперь хендл новоиспеченного логич. шрифта
00401171    PUSH    DWORD PTR [EBP-04]       ; hdc - я так и подписал в айсе
00401174    CALL    GDI32!SelectObject       ; в контекст устройства будет загружен хендл нов. шрифта
00401179    MOV     [EBP-48],EAX             ; а старый еще пригодится, надо его заныкать

    Думаю, если старый не восстановить, то винды не зависнут. Но если они это позволят, то весь текст во всех программах, которые через этот контекст устройства работают, будет иметь "красивый" шрифт. Хотя здесь слишком много фантазии. Iczelion писал: "Для видеодисплея контекст устройства обычно сопоставлен определенному окну на экране". Я это понял так: в другом окне мои данные не окажутся. А вообще, это тоже вопрос. (Q00B)
    Макрос RGB для установки цвета шрифта
    Как я уже сказал, нужно 24 бита (без прозрачности или особых параметров). Сомневаюсь, что прозрачность можно в этой проге вот так просто через EAX задать, а вдруг :).
    Нет, конечно, прозрачности я не получил. Как ни крути 4-й байт в EAX после макроса RGB, а если он не ноль, то цвет черный. Думаю, что для прозрачности нужны как минимум DirectX API.
0040117C    XOR     EAX,EAX    ; чистим EAX
0040117E    MOV     AH,32      ; в AH грузим 50d, это синий цвет (Blue)
00401180    SHL     EAX,08     ; сдвинем его на 8бит влево, он окажется в "начале" E-части
00401183    MOV     AH,C8      ; в AH 200d - зеленый цвет
00401185    MOV     AL,C8      ; в AL 200d - красный цвет

    Тут надо заметить, что если вы художник, но при этом не занимались компьютерной графикой и уже примерно прикинули в голове, что должно получиться, если взять одну часть синей и по четыре части зеленой и красной краски, то результат для вас будет неожиданный. Краски на холсте отражают, а краски на экране излучают (пока). Так что в RGB как все в компьютере наоборот, чем больше значение, тем ближе к белому, а не черному. Хотя я искренне не понимаю, зачем художнику, который не работает на компе, читать мой дневник :/?
    Короче, выше был получен насыщенный желтый цвет для текста. А в регистре это выглядит вот так: 00-Unused 32-Blue [B]С8[/B]-Grin [B]С8[/B]-Red.
00401187    PUSH    EAX                   ; желтый в стек как параметр
00401188    PUSH    DWORD PTR [EBP-04]    ; hdc дисплея, а мог быть и принтера или телетайпа :)
0040118B    CALL    GDI32!SetTextColor    ; вот она, GDI - мощная штука!

Макрос RGB для установки заливки (фона шрифта)
00401190    XOR     EAX,EAX
00401192    MOV     AH,FF
00401194    SHL     EAX,08
00401197    MOV     AH,00
00401199    MOV     AL,00

0040119B    PUSH    EAX
0040119C    PUSH    DWORD PTR [EBP-04]
0040119F    CALL    GDI32!SetBkColor

    Text Out
    invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString

    Напомню: SIZEOF - директива MASM'а.
    В данном примере она подсчитает символы в переменной TestString, и в exe'шник идет 22h
004011A4    PUSH    22                     ; с пробелами :) 33d + ноль = 22h, вот такая арифметика :)
004011A6    PUSH    00403020               ; тут если вы еще не поняли этой строки,
                                           ; то надо отдохнуть месяцок-другой,
                                           ; а потом с новыми силами взять хороший учебник и ...
004011AB    PUSH    00                     ; Y-координата позиции строки
004011AD    PUSH    00                     ; X-координата позиции строки
004011AF    PUSH    DWORD PTR [EBP-04]     ; ну а если отдыхать не хотите,
                                           ; то проводите побольше времени в отладчике
004011B2    CALL    GDI32!TextOutA         ; текст куда-то out, но на экране не появляется :/

    Что делает функция TextOut?
    В win32.hlp написано следующее:
    The TextOut function writes a character string at the specified location, using the currently selected font.
    Не знаю, как это правильно перевести, но я перевел бы так:
    Функция TextOut, используя выбранный в настоящий момент шрифт, записывает указанную строку символов.
    Куда записывает? Почему в tut5 она сразу не выводит на экран, как DrawText в tut4?
    Есть готовый перевод:
    Рисует строку текста, используя выбранный шрифт.
    Только это неправда, по крайней мере, на WinXP в пятом уроке. (Q00C)
004011B7    PUSH    DWORD PTR [EBP-48]    ; это старый шрифт, он все-таки привычней
004011BA    PUSH    DWORD PTR [EBP-04]    ; надо его на место вернуть в DC
004011BD    CALL    GDI32!SelectObject    ; что делает эта строка (Q00C)

Заканчиваем работу с DC экрана
004011C2    LEA     EAX,[EBP-44]          ; это указатель на ps
004011C5    PUSH    EAX    
004011C6    PUSH    DWORD PTR [EBP+08]    ; хендл окна
004011C9    CALL    USER32!EndPaint       ; почему-то именно в ходе выполнения этой
                                          ; строки появляется текст (Q00C)
004011CE    JMP     004011E5              ; DefWindowProc в этот раз делать нечего

004011D0    PUSH    DWORD PTR [EBP+14]
004011D3    PUSH    DWORD PTR [EBP+10]
004011D6    PUSH    DWORD PTR [EBP+0C]
004011D9    PUSH    DWORD PTR [EBP+08]
004011DC    CALL    USER32!DefWindowProcA
004011E1    LEAVE  
004011E2    RET     0010

004011E5    XOR     EAX,EAX
004011E7    LEAVE
004011E8    RET     0010

    Надо бы еще разок посмотреть всю прогу под отладкой, чувствую, что-то упустил :)).
    SoftIce говорит, что строка с текстом появляется раньше, чем я ожидал. Мало того, еще в tut4 от начала до конца отладки я так и не сделал. А не мешало бы. Там та же фигня получается. Помните, я вскользь писал, что API UpdateWindow нужна, если в окне есть элементы? Так текст - это тоже элементы. Получается вот что, если строку с вызовом этой API в айсе выполнить без захода, то текст и в tut4 & tut5 уже здесь будет на экране.
    Нужно было всего лишь доку по этой функции прочитать, еще один урок мне на будущее: новая функция - лезь в доку, а если не понял - в учебник, а впрочем, ну эти учебники, лучше сразу в отладчик и как следует все покрутить.
    Перевожу Win32.hlp:

    Функция UpdateWindow обновляет клиентскую область выбранного окна посредством посылки сообщения WM_PAINT. Если окно (или окна) не пусты, функция посылает сообщение WM_PAINT напрямую оконной процедуре, обходя очередь. Если окно пустое, сообщение не будет послано.
...

    Получается, что если в айсе выполнить строку CALL USER32!UpdateWindow, то отработает процедура окна (на сообщение WM_PAINT), а потом форточки вернутся к выполнению кода программы со следующей строки.
    Ну вот, опять заметку в прошлое писать придется :(.

    Ещё я заметил у Iczelion'a одну маленькую ошибку в примере tut5.
TestString  db "Win32 assembly is great and easy!",0

    Эта строка полностью повторяет tut4, и там она уместна, так как DrawText нуждается в нулевом байте в конце текста. А вот TextOut узнает конец строки из своего параметра cbString (число символов в строке). Получается, что этот нулевой байт выводится на экран (подсчитывается вместе с текстом). С "красивым" шрифтом символ, заменяющий 00h, почти незаметен (просто точка), но если взять стандартный шрифт, то вместо любого неопределенного в таблице символа будет "Б"яка. Я эту незначительную ошибку исправлять не стану, чтоб мои примеры как можно меньше отличались от  ваших.

    Вопросы на сегодня:

    (Q00A) DEFAULT_PITCH or FF_SCRIPT - интересно, как это складывается?

    Как MASM внутри invoke складывает через "or" и самое главное - это в форточках норма?
    В файле WINDOWS.INC описаны константы, в том числе и

DEFAULT_PITCH equ 0, а также
FF_SCRIPT equ 64       (equ - эквивалент)

    (Обратите внимание, что по умолчанию в inc-файлах десятичные значения.)
    Тут, в общем, и складывать нечего. Для более понятного эксперимента мне нужны две ненулевые константы, а лучше три.
    Вот в tut4 у функции DrawText как раз три константы через or

DT_CENTER           equ 1h
DT_VCENTER         equ 4h
DT_SINGLELINE    equ 20h
    А в отладчике эти константы сливаются в PUSH 25
    Как будто просто складываются. А как тогда форточки (то есть функции) узнают, что есть что?

    Вот все константы функции DrawText:
DT_TOP              equ0h
DT_LEFT             equ0h
DT_CENTER           equ1h
DT_RIGHT            equ2h
DT_VCENTER          equ4h
DT_BOTTOM           equ8h
DT_WORDBREAK        equ10h
DT_SINGLELINE       equ20h
DT_EXPANDTABS       equ40h
DT_TABSTOP          equ80h
DT_NOCLIP           equ100h
DT_EXTERNALLEADING  equ200h
DT_CALCRECT         equ400h
DT_NOPREFIX         equ800h
DT_INTERNAL         equ1000h
DT_EDITCONTROL      equ2000h
DT_PATH_ELLIPSIS    equ4000h
DT_END_ELLIPSIS     equ8000h
DT_MODIFYSTRING     equ10000h
DT_RTLREADING       equ20000h
DT_WORD_ELLIPSIS    equ40000h

    Люди с математическим уклоном, уже по цифирному ряду поняли, что к чему, а те, кто с математикой не дружат (вроде меня), думают не совсем в том направлении (если внизу не подсмотрели ;).
    Мне сначала показалось, что эти числа не пересекаются, кроме двух значений TOP & LEFT - они просто равны. Правда, совпадений нет только при условии, что значения прибавляют один раз. Например:
VCENTER "+" VCENTER = BOTTOM
    Потом присмотрелся, и дошло (папа объяснил =), тут суть не в hex-, а в двоичных циферках. Калашников так и предупреждал, что все три системы пригодятся, а я и забыл про то, что комп всё в нолики и единички заворачивает.
    И так перевожу на язык компьютера:
DT_TOP              0000000000000000000
DT_LEFT             0000000000000000000
DT_CENTER           0000000000000000001
DT_RIGHT            0000000000000000010
DT_VCENTER          0000000000000000100
DT_BOTTOM           0000000000000001000
DT_WORDBREAK        0000000000000010000
DT_SINGLELINE       0000000000000100000
DT_EXPANDTABS       0000000000001000000
DT_TABSTOP          0000000000010000000
DT_NOCLIP           0000000000100000000
DT_EXTERNALLEADING  0000000001000000000
DT_CALCRECT         0000000010000000000
DT_NOPREFIX         0000000100000000000
DT_INTERNAL         0000001000000000000
DT_EDITCONTROL      0000010000000000000
DT_PATH_ELLIPSIS    0000100000000000000
DT_END_ELLIPSIS     0001000000000000000
DT_MODIFYSTRING     0010000000000000000
DT_RTLREADING       0100000000000000000
DT_WORD_ELLIPSIS    1000000000000000000

    От нулей в глазах рябит.
    Теперь все проще некуда
DT_TABSTOP or DT_END_ELLIPSIS в физических единицах памяти = 0001000000010000000 (естественно, я взял только 19 регистров. Мне кажется, что нулей и так достаточно :)
    Каждая константа - один двоичный регистр. Их даже подписать можно, если не лень.

    Вывод: внутри invoke "or" делает то же самое, что опкод "or" (логическое побитовое сложение), только форма записи более высокоуровневая:
    первый операнд or второй операнд or следующий...
    Вторая часть вопроса (это в форточках норма?) отпадает сама собой. Битовые параметры, как я понимаю, есть не только в форточках, они должны быть везде, где есть биты и команда or (или что-то похожее на неё).

    (Q00B) "Для видеодисплея контекст устройства обычно сопоставлен определенному окну на экране". Так пишет Iczelion. Это значит, что у каждого окна DC свой?
    Сейчас не могу сказать точно, потому что handle DC общий.
    Проверить это очень просто, загрузить tut4 & tut5 и посмотреть в айсе после BeginPaint переменную hdc (EBP-4). Во всех случаях это будет один и тот же хендл.
    Но другой эксперимент показывает, что значения из одной программы в другую не попадают, по крайней мере, я сделал такой вывод. Вот сам эксперимент:
    Создаю два альтернативных tut5 с небольшими различиями. Первый (tut5_a) не восстанавливает в контексте шрифт:
; не нужна            invoke SelectObject,hdc, hfont  (это 10 строка снизу в tut5)

а второй (tut5_b) не меняет его вообще (пользуется тем, что дадут):
; можно просто удалить    invoke SelectObject, hdc, eax   (это 17 строка снизу)

    Открываю оба варианта, и... не вышло.
    Суть в том, что если б изменения контекста дисплея передавались из tut5_а в tut5_b, в окне второго текст рисовался бы тем же "красивым" шрифтом, что и в первом, а этого не происходит. Конечно, я ничего не знаю о том, как этот контекст передается, но скорее всего между программами данные в нем передать нельзя. А жаль, можно было бы так повеселиться. Зачем Iczelion пишет, что подмененный шрифт нужно вытащить и положить старый?
    Может быть, в пределах одного окна данные сохраняются.
    Рассмотрю-ка я исходник получше...

    Вкратце, при сообщении WM_PAINT, в проге будет вот такая заварка:
    BeginPaint просит DC
    CreateFont создает логический шрифт
    SelectObject меняет в DC шрифт
    TextOut куда-то "сообщает" строку для вывода
    SelectObject зачем-то восстанавливает старый шрифт
    EndPaint отображает текст и отдает DC

    Забуду пока про TextOut. А для HDC нужен еще эксперимент.
    Ставлю задачу: изменить tut5 так, чтоб в его окне было две строки текста. Каждая строка должна рисоваться на отдельное сообщение WM_PAINT. Первый раз отрисовка будет менять цвет и шрифт, но не будет их восстанавливать. Второй раз отрисовка будет происходить из тех параметров, которые окажутся в контексте устройства. Рекомендую вам попробовать решить эту задачу самостоятельно, а потом сравнить с моим вариантом.

    Давненько я не брал в руки шашки. :
...
    У меня на это ушло примерно полтора часа :), вот что значит: нет практики.
    Рассказываю:
    .data                        ; в секцию данных
......
    PaintTest BYTE 00h             ; добавляю инициализированную переменную размером в байт
......
; Ну, не вставлять же мне весь код.
; Это "бейсик" из WndProc вперемешку с моим творчеством :)

     .IF uMsg==WM_DESTROY
    invoke PostQuitMessage,NULL
     .ELSEIF uMsg==WM_PAINT        ; если в tut5c поступило WM_PAINT, то:

  push EAX                         ; проще его сохранить, чем выяснять, нужен ли он
  xor EAX,EAX                      ; Dispatch вышлет в EAX не ноль, поэтому обнуляю
  mov AL,PaintTest                 ; знач. новой переменой в регистр AL
  test AX,AX                       ; проверю на ноль
  jnz NextHDC                      ; если не ноль, то на экране уже должен быть "красивый" текст
  inc PaintTest                    ; если был ноль, то теперь не ноль
  pop EAX                          ; восстановлю EAX

;======== пошло рисование ========
           invoke BeginPaint,hWnd, ADDR ps
    mov    hdc,eax
    invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,
                    OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,
                    DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,
                    ADDR FontName
    invoke SelectObject, hdc, eax
    mov    hfont,eax
    RGB    200,200,50
    invoke SetTextColor,hdc,eax
    RGB    0,0,255
    invoke SetBkColor,hdc,eax
    invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString

; ну не хочу я восстанавливать шрифт,    REM тебя, REM =)  invoke SelectObject,hdc, hfont
            invoke EndPaint,hWnd, ADDR ps
jmp def                             ; перепрыгиваем через второй вариант

NextHDC:                            ; на эту метку мы попадем, если в PaintTest был не ноль
  dec PaintTest                     ; а теперь сделаю его опять нулем
  pop EAX                           ; и восстановлю EAX

;======== пошло другое рисование (того, что Бог подаст) ========
           invoke BeginPaint,hWnd, ADDR ps
    mov    hdc,eax
invoke TextOut,hdc,50,50,ADDR TestString,SIZEOF TestString    ; чтоб строка не перекрывала первую - "50,50"
           invoke EndPaint,hWnd, ADDR ps

def:                                ; эта метка нужна только для jmp def
     .ELSE
    invoke DefWindowProc,hWnd,uMsg,wParam,lParam
    ret
     .ENDIF

    Этим я занимался 1:20 и назвал tut5_c. А потом почитал help'ы от MASM'a и за десть минут сварганил красивый вариант в стиле бейсик (tut5_d). Весь код я писать не буду, приведу лишь ключевые строки.
.ELSEIF uMsg==WM_PAINT && PaintTest==0        ; && - логическое И (AND)
    inc PaintTest
...... (здесь первая рисовка)
.ELSEIF uMsg==WM_PAINT && PaintTest!=0        ; == это равно
    dec PaintTest                             ; != это не равно
...... (а здесь вторая)

    Если вы загляните в файл masm32.hlp, то сами все поймете. Оба варианта работают одинаково, но второй выглядит лучше, хотя размер exe - байт в байт, я даже удивился.
    Ну что, загрузил я этот tut5_d. Развернул на весь экран - стандартный шрифт и цвет. Обратно в окошко - "красивый" шрифт и цвет. И так раз сто. Пока я этим познавательным делом занимался, все думал: "На хрена все-таки старый шрифт сохранять, а потом на место восстанавливать, если он все равно с каждым возвратом контекста устройства сам сбрасывается?" (Q00C)
    Уж больно непонятный этот DC, мне в голове нужно образ нарисовать, чтоб хоть как-то его осознать.
    Пофантазирую немного.
    Закрываю глаза и вижу: огромное здание, мраморные ступени, полированная медная вывеска:
                         Главное Государственное Управление
                                      WindowsXP
                              (на основе технологии NT)

    Мощная дверь. Через просторный холл прохожу в лифт, жму второй этаж, передо мной громадный зал, но вся мебель стоит у входа, а дальше пустота. Слева конторский шкаф, в нем папки, подписанные маркером: user32.dll, kernl32.dll, gdi32.dll и еще кое-что. Справа, прямо посередине зала, дверь, табличка на ней старая и привычная - "API". Из папки достаю закладку UpdateWindow, открываю дверь, а там зеркальный коридор с бесконечным количеством отражений. Понять, где стена, а где следующая дверь, невозможно, народу вроде бы немного, зато в зеркалах отражается не только свет, но и звук. Вечное эхо захлопывает за мной зеркало, и какой-то невероятной силой меня тащит вдоль по коридору. От меня уже ничего не зависит, становится легче. В отражении я вижу: мрачный галстук, ноги в старых, но начищенных до блеска туфлях и табличку, прикрепленную к дешевому пиджаку: "tut5 - приложение третьего звена". Дальше я прохожу несколько кабинетов, получаю печати и подписи, но без особого интереса, вокруг все бегают, суетятся. Меня догоняет маленький, но шустрый парнишка с фуражкой "PostMessage": "Господин tut5, вам письмо". На конверте написано: доставка в очереди. Открываю конверт, на фирменном бланке заголовок "WM_PAINT", а чуть ниже изложена рекомендация явиться во внутренний отдел BeginPaint за формой для экрана. Наконец-то! Захожу, никого не вижу, высокая стойка, а за ней дымок в полутьме настольной лампы, подхожу поближе, сидит форменная жаба, пьет какую-то гадость.
    - А, tut5, где же вас так долго... экран-то один на всех, не задерживайте работу, вот форма DС дисплея, и поскорей давайте.
    Форма как форма, некоторые поля заполнены, в некоторых галочки нужно поставить, вписываю свой шрифт, цвет, еще кое-какие параметры и строку с текстом.
    - Пожалуйста, все заполнил.
    - Так... все верно, только подчерк корявый. Теперь идите в РисОвочную.
    О, великий драйвер видеокарты,  прими DC.
    Колдуй баба, колдуй дед.
    - Вы куда собрались, у вас тут в форме шрифт менялся, так будьте любезны выбрать старый.
    ...
    Бах-тарарах, меня на весь экран растянули, надо заново форму заполнять.
    - Мадам, а нельзя ли мне воспользоваться старой формой?
    - Я тебе не модам, а госпожа оформитель! Ишь чего придумал - старую форму. А как я за тебя отчитываться буду, А?
    - Хорошо, давайте я все заново заполню, только зачем в прошлый раз возвращал шрифт...
    - Думаешь, самый умный! Думаешь там... наверху, глупее тебя сидят, а я вот сейчас позвоню куда надо, так в следующем конверте WM_ DESTROY будет, но ты его уже не увидишь.
    - Уж лучше WM_ DESTROY, чем формы заполнять не понимая, к чему все это.
    Открываю глаза. Мама! Сколько я ерунды накатал!
    Пойду-ка посплю-ка.

5 сентября


    Да, в прошлый раз меня занесло. Но это даже забавно, поэтому стирать не стану, теперь вы можете представить, какую фигню я стираю поутру (или вечером, или днем :).
    На вопрос, зачем восстанавливать шрифт в DC, я бы пока ответил так: "а чтобы было". В смысле: в старых виндах (или в новых), может быть, замененные объекты и не восстанавливаются сами, или в других, более сложных ситуациях глюки возникнут. Iczelion зря писать не стал бы, не то что я :).

    Я надеюсь, что про потоки вы начальные сведения прочли, а если нет, то прочтите. Переписывать чужие книги я не хочу, а своего добавить нечего, все, что я прочел, в 6-й главе у Рихтера.
    Сегодня я буду отвечать на один вопрос. Мне он кажется интереснее, чем простой разбор следующего урока (это дело вы можете делать и сами), хотя я обязательно продолжу уроки, но сейчас вопрос номер 9.

    (Q009)  Команда RET 0010. Куда возвращаемся из WndProc?
    Сразу же поправлю формулировку вопроса: лучше обозвать ret не командой, а строкой или опкодом. Именно из-за слова "команда" обсуждение ушло не туда (есть ret-команды айса).
    Ответов пришло много и почти все толковые. К сожалению, у меня еще слишком много незнаний, чтоб без заблуждений разобрать процесс работы программы в WinXP. Как мне объяснили в той же дискуссии, здесь есть разница между Win9x, WinNT и даже XP.
    Из форума я понял, что тема не для чайников и сразу же понять, что после чего, можно только через конкретный случай, а в другом случае могут быть совсем другие выводы.
    Еще пару дней назад я был уверен, что строка с текстом в tut4 & tut5 появится после цикла сообщений, а сегодня я знаю, что UpdateWindow посылает внеочередное сообщение "прямо" WndProc.
    В этом случае отрисуется текст, выполнится строка ret 10, и после многих функций форточки вернут управление в цикл сообщений проги (на следующую после UpdateWindow строку).
    Описание очень туманно. Что такое многие функции?
    Многие функции - это прежде всего ядро операционной системы, так как обработка сообщений происходит в ядре (в драйвере win32k.sys).
    Именно поэтому прямо в кавычках:

прямо через: user32 UpdateWindow > ... > win32k.sys > ... > user32 PostMessage > WndProc
    В процедуре окна выполнится все что нужно, а потом весь путь ret'ами назад.

    Вы при помощи айса можете сами отследить весь "прямой" путь, правда, не во всех случаях это получается, но в этом (с API UpdateWindow на XP в tut4 & tut5) получается точно.

    На счет других способов вызова процедуры окна и возврата из нее я пока мало знаю, однако в голове уже каша. Хорошо бы кашу слопать, а в голову загрузить handl'ы полезных мыслей :).

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

    Возвраты из процедуры окна разные, по многим причинам, например: самый очевидный вызов оконной функции - это DispatchMessage. Тогда есть вероятность, что после ret 10 в WndProc винды последовательно выполнят все что положено и несколько ret'ов вернут нас в цикл, на следующею за Dispatch строку. Но есть и такие, казалось бы, странные вызовы, как DefWindowProc или GetClientRect, подобные функции находятся в самой процедуре окна, т.е. вызов происходит непосредственно из WndProc рекурсивно. Вспомните барона Мюнхгаузена, он утверждал, что вытащил себя за волосы из болота, а барон Мюнхгаузен никогда не врет! Так вот в таких случаях ret 10 в WndProc, естественно, обязана вернуть нас (тоже не напрямую) обратно в WndProc, чтобы она отработала до конца. И опять тот же ret 10. Таких "витков" может быть очень много. Возврат будет длинным :/.
    Я написал: "Тогда есть вероятность", потому что, во-первых, многозадачность просачивается почти во все щели в программе, а во-вторых, есть nonqueued-сообщения, которые могут обрабатываться после процедуры окна перед циклом сообщений. Именно поэтому так непросто понять, что дальше произойдет.
    Я знаю, что до Win95, форточная многозадачность заканчивалась на той программе,  которая не отдавала управление системе. То есть, образно говоря, если из tut5 убрать вызов GetMessage и запустить ее в Win 3.1, образно говоря, то, кроме мышки, больше ничего работать не будет. Мышь тоже не во всех случаях выживет (если она таскается не аппаратно, то кирдык и мышке). Понятно, что в WinXP механизм получше, но насколько и как он работает, я пока сказать не могу.
    Сейчас я знаю только такие способы усыпить поток. После вызова GetMessage, если в очереди нет сообщений, то ядро передает управление другому потоку до тех пор, пока сообщения не появятся. Также мне кажется, что после всё той же ret 10 в WndProc поток может уснуть.
    Leo, один из участников форума, высказался так: насколько я понимаю, PeekMessage отличается от GetMessage тем, что она возвращается из ядра независимо от наличия сообщения в очереди. Т.е. if Peek else Wait - это и есть Get. Точнее сказать, эквивалентом Get является Peek + проверка: на WM_QUIT - если true, или Wait - если false.
    Про ядро и устройство Windows (схематично) можно прочитать во многих книгах. Почитаю еще Рихтера и посмотрю другой полезной литературы. Хорошо бы про защищенный режим почитать и о моделях памяти, а то я запутался, есть все-таки в Win32 сегменты или нет.

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

    Еще раз повторю: после ret 10, до ядра, мы попадаем туда, откуда была вызвана WndProc. Т.е. куда-то в user32.dll. В XP это будет проверка выравнивания стека:
cmp [dword ss:esp+4], DCBAABCD

так виндос заботится о тех, кто использует RET вместо RET 10h.
    Дальше, как я уже писал, возможны разные варианты.

    Вернуть управление система должна в то место, где приостановила поток (при нормальной работе).
    Nonqueued (внеочередные) сообщения передаются системой в WndProc "напрямую". Это куча системных сообщений (типа WM_ACTIVATE и т.д.), а также сообщения, передаваемые через SendMessage.

    А насчет пункта 1 из Рихтера, так там речь идет об особом виде сообщений QS_SENDMESSAGE - а message sent by another thread or application - сообщение, которое было передано из другого потока или приложения (см. GetQueueStatus).

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

    За вразумления меня, чайника, выражаю благодарность участникам форума wasm.ru
Four-F
Leo
S_T_A_S_
Спасибо!

14-19 сентября


    Почитал книги, help'ы, другую инфу и задумался, а не переписать ли мне все по новой, но вовремя остановился. Мой дневник, упорядоченный и переделанный, не имеет никакого смысла. Я, правда, сомневаюсь, что он имеет смысл в таком виде :), но цель была: не особо опираясь на доку и учебники, обнаружить самые явные вопросы в той последовательности, в которой они возникают у человека с моим багажом user'ских знаний. Это оказалось не так просто. Вопросы сыплются пачками и совершенно из разных областей. Я понял, что без хороших учебников дело будет бесконечно долгим.
    Ищу учебники по ассму и по архитектуре форточек. Ищу не чего скачать или купить, а чего стоит читать и обсуждать. Найду, скажу. Многие рекомендуют Зубкова, сам я про его книгу "Ассемблер язык неограниченных возможностей" сказать могу только так: то ли в сети не полная версия, то ли я еще не прочувствовал всей прелести столь коротких ответов на основные начальные вопросы (хотя идея со сварщиком на обложке очень даже ничего :).
    Ранее упомянутая книга Рихтера ориентирована на высокоуровневых программистов. Поэтому любое приближение к архитектуре процессора немедленно пресекается абстрактными категориями.
    
    За скромный период ведения дневника я понял важную вещь - если перед тобой камнем вырос вопрос, есть три пути:
    налево пойдешь, выход найдешь (устал, сдаюсь)
    направо пойдешь, условия примешь (выучить, как оно должно быть, и считать вопрос решенным)
    прямо пойдешь, сквозь камень пройдешь и изнутри все сам увидишь.

    Прямой путь мне кажется самым привлекательным. Его суть заключается в принципе понижения уровня. Если вопрос не в электропроводке за компьютером :), то в компе всегда есть уровень ниже (ближе к физическому основанию). Там, если оглядеться, обязательно есть ответ... и следующий вопрос :).
    Дальше я постараюсь как можно больше вопросов решать "сквозь камень". Но сами понимаете, все так не пройдешь, жизни не хватит, да и "лень" бывает сильнее любознательности.
    Вот графомания чертова! Полчаса угробил!

    Начну с глупости.
    Главная моя глупость (которую я увидел) заключается в первоначальном отсутствии интереса к help-файлам MASM'a. Там, оказывается, столько полезного. Описание команд, директив и всяких фишек. В общем, там есть такие строки, которые для новичка вместо 2-3 часов поиска.
    Если у вас совсем плохо с английским, то самое время это исправить, так как вся свежая документация только на english'е. Проще язык немного подтянуть, чем самому все раскапывать. Вот сейчас я свой английский и буду подтягивать.
    Во второй чтиве я не мог найти толкования термина pointer и воспользовался переводом словаря Lingvo. А определение было у меня под носом (в тех же help'aх). Вот мой перевод одного раздела из asmintro.hlp:

    

Адреса и Указатели (Addressing and Pointers)


    Важно понять различия в ассемблере  между АДРЕСОМ переменной и ее ЗНАЧЕНИЕМ.
АДРЕС переменной - ГДЕпеременная располагается в памяти,
ЗНАЧЕНИЕпеременной - то, что находится по этому адресу.

    Это АДРЕС переменной в памяти
                            Память

          [][][][][][][][][X][X][X][X][][][][][][][]
                          |
                          Адрес переменной


    Это ЗНАЧЕНИЕпеременной по этому адресу.

                      [X][X][X][X]
                   Значение переменной

    В ассемблере существует метод получения значения переменной по адресу, эта техника называется "снятие косвенности" (dereferencing - получение значения объекта, к которому отсылает данный указатель).
mov   eax,lpvar    ; копирует адрес в регистр eax
mov   eax,[eax]    ; снимает его косвенность
mov   nuvar,EAX    ; копирует eax в другую переменную

    Поместив EAX в квадратные скобки, мы получаем доступ к информации, расположенной по адресу в EAX. Такой способ будет работать с любыми 32-разрядными регистрами. Регистр, взятый в квадратные скобки, - это действующий (effectively) операнд памяти. Размер данных, полученных по адресу, будет определен размером использованного для получения регистра. В данном примере 32-битное значение, так как использован 32-битный регистр, но значение может быт 16- или 8-битным, если использовать регистр соответствующей разрядности.

    Указатели (Pointers)
    Указатель - это распространенное, в языках высокого уровня, понятие (abstraction), используемое для пересылки адресов между процедурами и для выполнения других типов сложных манипуляций с данными.
    В ассемблере, когда вы используете инструкцию, подобную этой:
lea   eax, MyVar

помещается АДРЕС переменной в регистр EAX. Когда вы выполняете следующий шаг и помещаете этот АДРЕС в собственную переменную, вы получаете УКАЗАТЕЛЬ на адрес:
mov lpMyVar, eax

    Механизм этого процесса сложно осмыслить, поэтому могут быть серьёзные ошибки, которые трудно выловить.
    Вы можете передать указатель другой процедуре следующими способами:
через значение указателя,
mov eax, lpMyVar    ; копировать ЗНАЧЕНИЕ в eax
push eax            ; поместить его в стек как параметр
call MyProcedure    ; вызвать процедуру

или через ссылку на него
lea eax, lpMyVar    ; загрузить АДРЕС в eax
push eax            ; поместить его в стек как параметр
call MyProcedure    ; вызвать процедуру

    Когда вы передаете адрес в такой манере, вы добавляете сверхуровень перенаправлений (extra level of indirection). Таким образом, после подобной передачи вы имеете ссылку на ссылку на адрес. Чтобы в процедуре получить адрес, вы должны снять косвенность (dereference) с переменной. Так вы получите оригинальный адрес
mov eax, lpMyVar
mov eax, [eax]

    Теперь в eax окажется оригинальный адрес.
...
    Уф. Перевел...
    Это не так сложно (прочитайте, что делает LEA, и сравните с MOV). Есть значения, есть указатель, а есть указатель на указатель на значение. Но в реальных условиях значения могут быть спрятаны гораздо глубже. Поэтому особенно важно понять дереференсинг. Думаю, что снимать косвенность (хотя бы в уме) при исследовании программ придется постоянно.


Урок 6


    Прочитал, кампильнул, запустил. Первый, невооруженный взгляд не увидел ничего принципиально нового. Разве что урок этот в прямом виде вряд ли пригодится. Ну, сами подумайте, во многих прогах есть строки с текстом, MessageBox'ы и окна в принципе. А вот чтоб один символ на экране печатался, я что-то такой проги не припомню. Полезность урока, правда, от этого не уменьшается, а может, даже и увеличивается. Так и подмывает переделать tut6 а-ля notepad :), но это не сегодня.
    Пора вооружать взгляд софтайсом... Опять фокусы! А как оно работает?
    В данной tut'те интересна обработка сообщения WM_CHAR, поэтому из айса я вставлю не всю прогу, а только куски, причем порядок в программе меня не устраивает. Обратите внимание, что куски будут идти не так, как они лежат в памяти, а так, как они выполняются.
    Ставлю бряк на строку 401133, жму клавишу и вижу следующие:
0040112A    CMP    DWORD PTR [EBP+0C],00000102  ; сообщение WM_CHAR имеет значение 102
00401131    JNZ    0040114A                     ; поступило именно оно, поэтому идем дальше
00401133    PUSH   DWORD PTR [EBP+10]           ; толкаем в стек из фрагмента полученный wParam
00401136    POP    DWORD PTR [00403020]         ; вынимаем его в переменную char
0040113C    PUSH   01                           ; нужно ли уничтожать бэкграунд - TRUE
0040113E    PUSH   00                           ; указатель на обновляемый прямоугольник - ну его,
                                                ; обновлять, так сразу всё!
00401140    PUSH   DWORD PTR [EBP+08]           ; handle окна
00401143    CALL   USER32!InvalidateRect        ; вот это новая вещь
00401148    JMP    00401195                     ; прыг на завершение

    Как всегда то, что Iczelion написал, а Aquila перевел, до меня доходит только в отладчике и то не сразу.
    После того как InvalidateRect положит в обновляемую область (думаю, так переводится update region) что-нибудь, форточки добавят в очередь сообщение WM_PAINT (читай win32.hlp). И всё. JMP 401195 уводит нас в эпилог процедуры окна.
    А компот?
    За ним придется сходить обратно в цикл сообщений. Я уже знаю, что вернуться мы должны на следующую за вызовом строку при помощи ret'ов во всех процедурах, которые были по пути к WndProc. Вызывал в этот раз DispatchMessage, а за ним строка: jmp в начало цикла сообщений.
004010E7    PUSH   00    
004010E9    PUSH   00    
004010EB    PUSH   00    
004010ED    LEA    EAX,[EBP-4C]    
004010F0    PUSH   EAX    
004010F1    CALL   USER32!GetMessageA       ; получаем сообщение WM_PAINT
004010F6    OR     EAX,EAX    
004010F8    JZ     0040110E    
004010FA    LEA    EAX,[EBP-4C]    
004010FD    PUSH   EAX    
004010FE    CALL   USER32!TranslateMessage    
00401103    LEA    EAX,[EBP-4C]    
00401106    PUSH   EAX    
00401107    CALL   USER32!DispatchMessageA  ; он доставит новое сообщение
0040110C    JMP    4010E7                   ; сюда в этом случае вернет ret из WndProc

    Чтоб не блудить по виндам, можно поставить еще один бряк на следующую строку и отпустить на секунду айс.
0040114A    CMP    DWORD PTR [EBP+0C],0F    ; а теперь хорошо бы бряк отключить
0040114E    JNZ    00401180    
00401150    LEA    EAX,[EBP-44]    
00401153    PUSH   EAX    
00401154    PUSH   DWORD PTR [EBP+08]    
00401157    CALL   USER32!BeginPaint        ; здесь обновляется фон клиентской области и получается HDC
0040115C    MOV    [EBP-04],EAX    
0040115F    PUSH   01    
00401161    PUSH   00403020                 ; толкаем в стек указатель на char
00401166    PUSH   00    
00401168    PUSH   00    
0040116A    PUSH   DWORD PTR [EBP-04]    
0040116D    CALL   GDI32!TextOutA           ; куда-то отправится либо строка текста,
                                            ; либо указатель на неё, а также параметры.
                                            ; Пока только догадки (Q00C)
00401172    LEA    EAX,[EBP-44]    
00401175    PUSH   EAX    
00401176    PUSH   DWORD PTR [EBP+08]    
00401179    CALL   USER32!EndPaint          ; появляется текст, и отдается DC
0040117E    JMP    00401195

    Ну и для наглядности эпилог
00401195    XOR    EAX,EAX    
00401197    LEAVE        
00401198    RET    0010

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

    Нашел у себя в закромах описание понятия контекст устройства:

    Давайте определим понятие контекста устройства и контекста отображения. Контекст устройства выступает в роли связующего звена между приложением и драйвером устройства и представляет собой структуру данных размером примерно 800 байт. Эта структура данных содержит информацию о том, как нужно выполнять операции вывода на данном устройстве (цвет и толщину линий, тип системы координат и т.д.).
Если приложение получает или создает контекст для устройства отображения, такой контекст называется контекстом отображения. Поэтому когда, например, приложение получает контекст для отображения в одном из своих окон, такой контекст называется контекстом отображения. Если же ему требуется выполнять операцию вывода для устройства (для принтера или для экрана дисплея), приложение должно получить или создать контекст устройства. Однако следует понимать, что контексты устройства и отображения содержат описания одних и тех же характеристик и имеют одинаковую структуру. Название контекста определяется только тем, относится ли контекст к окну отображения или устройству вывода.
......
    Вот, блин, найти бы мне этот файлик раньше. Я понятия не имею, откуда он у меня. Но это html значит почти наверняка: он есть в инете. К сожалению, я сейчас в полной изоляции от всего мира. Название такое: "Глава 4: Вывод текста". Поищите, может, там еще чего полезного найдете. Кроме того, даже если есть только четвертая глава, ее тоже всю прочесть полезно.
Пойду писать заметку в свой первый словарь.
Только я эти контексты все равно не понимаю.
Надоело мне в потемках бродить, людей и себя путать! Нужен MSDN. В ближайшие дни поеду по магазинам за литературой, дисками и заодно мышку новую куплю, а то уже непонятно, чья мышь, все что можно, стерлось, провод рвался пару раз, кнопки как положено перепаивались. На рекорд тянет, уже три компа в прошлое ушли, а она еще живая :).

29 сентября-1 октября


    Много дней всякой ерундой занимался: то некогда, то форматировал первые две чтивы. Доступ в сеть появился, хоть какой-то.
    Вот... радость у меня. Опубликовался на cracklab'e! Теперь я там автор, хоть и чайник чайником :).
    Как только инет появился, я сразу на MSDN и там про TextOut поискал. Зря я на MSDN так рассчитывал. В магазин так и не выбрался, хотя надо.

    (Q00C) Как EndPaint отображает строку с текстом в tut5 & tut6? Рисует ли TextOut хоть куда-нибудь строку и причем тут DC?

    Исходник tut5:
    invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString    ; "сообщает" строку и параметры
    invoke SelectObject,hdc, hfont                              ; восстанавливает шрифт
    invoke EndPaint,hWnd, ADDR ps                               ; отображает строку

    Если рассуждать логически, то после SelectObject,hdc,hfont в контексте будет выбран стандартный шрифт. Но строку после EndPaint мы увидим "красивую". Вывод - TextOut подготавливает строку с параметрами так, чтобы было неважно, какой в контексте выбран шрифт. Иными словами, строка вряд ли идет в контекст.
    Только эта логика здесь, похоже, не работает. Я два дня этот фокус изучаю. Вот с чего я начал.
-----------------------------
    Мне, честно говоря, надоели всякие высокоуровневые абстракции. Попробую разобрать это дело в айсе...
    Да, к сожалению, тут не все так очевидно, как хотелось бы. Жму клавишу, например "х" и трассирую от EndPaint до появления символа на экране. Сначала появляется фон, потом появляется сама "х". Происходит это в драйвере видеокарты, поэтому бесполезно описывать, как именно символ оказывается на экране, в других дровах, процесс, скорее всего, пойдет не так. Своё любопытство каждый может удовлетворить на своей видяхе (отследите и потыкайте разные байты в памяти). Только хочу предупредить: у меня несколько раз пропадал видеосигнал (или включался недоступный видеорежим), а к чему еще это может привести, один Бог знает. Будете экспериментировать, побольше молитесь и пеняйте на себя; если что, я не виноват =).
    Если вам лень самим отслеживать этот тернистый путь, но все же интересно, что там происходит, могу сказать, что после выполнения следующей строки (в моей машине) символ появляется на экране.
MOV [EDX],ECX
    Все тот же ассемблер и никакого колдовства :). Понятно, что строчка бесполезная. В ECX не Unicode-символ, а EDX указывает не на видеобуфер (как это могло быть в DOS'e). Но все же что-то конкретное куда-то конкретно передается - и буква на экране. И потом в EDX все же указатель чего-то "похожего" на видеобуфер.
    Отвлекся. Я искал, как EndPaint просит вывести символы на экран. Теперь мне интересно, где символ берется и кто его туда кладет...
    Для наглядности изучу этот процесс на tut5 (там не один символ, а целая строка).
    В ходе выполнения сама TextOut вызывает всего 3 API-функции, вот они:
    GdiGetCodePage - тут и пояснять нечего
    MultiByteToWideChar - она переводит строку в Unicode (если кто не знает, WideChar= Unicode)
    GdiProcessSetup - самая интересная API, в ней и происходит "вывод" текста.
    В доке я её не нашел (в MSDN на сайте ее тоже нет), так что словесного описания от производителя у меня нету. Но в асмовом виде кое-что понятно. Надо учесть, что дизайн в XP новый, поэтому GDI может работать совершенно не так, как в предыдущих форточках (не проверял).

LEA    EDI,[EDX+50]    ; EDX, получается, указывает на... не знаю, как это назвать, назову Х-структура  :)
REPZ   MOVSD           ; Move String dword цикл (читай opcodes.hlp из MASM'a)

    Repz-цикл повторяется нужное количество раз. Символы, переведенные в Unicode, занимают два байта. MOVSD за раз копирует четыре байта = 2 символа.
    Количество символов в строке может быть четным или нечетным (как в tut5 без нулевого байта на конце строки). Если четное, например tut5 с нулевым байтом:
    34символа*2байта в символе=68байта/4dword=17= повторений цикла
    В счетчике ECX будет 11h=17d
    Если нечетное, например:
    33символа*2байта в символе=66байта/4dword=16 повторений цикла и половинка
    В счетчике ECX будет 10h=16d
    Остаток в цикле с movsd обработать нельзя, поэтому существует продолжение:
MOV    ECX,EBX    ; В счетчик ECX из EBX копируется число байт в строке 42h
AND    ECX,3      ; Из 42h байт получаем остаток 2 байта (читай П. Абеля)
REPZ   MOVSB      ; Move String byte цикл (читай opcodes.hlp или Абеля)

    Вот примерно так.
    Но это не суть, а действие (и конечно, не все). Суть в том, что куда копируется.
    Первая строка. В EDI окажется указатель на место, куда цикл скопирует первый символ строки, я это место в шутку обозвал Х-структура+50.
    Моно было бы предположить, что это DC, но как я писал ранее, так не должно быть.
    Я просмотрел всю эту функцию и не нашел больше никаких действий по рисовке. Иными словами: ничего TextOut не рисует, она просто копирует строку из одного места в другое при помощи GdiProcessSetup.
    Из этого другого места при выполнении строки EndPaint символы отправляются на рисовку и в видяху. Причем буквиц нарисуется столько, сколько сообщает в Х-структуру TextOut (в tut5 - 34), а не как в видеобуфере в DOS'e: все, что есть, - отображается.
    Пока экспериментировал, узнал, что GdiProcessSetup вызывается много раз (не напрямую) в ходе отрисовки самого окна, меню, клиентской области и всего GUI.
    При таком раскладе Х-структура уж больно похожа на контекст устройства дисплея. В нее же чего-то добавляется при изменении шрифтов и параметров DC.
    Надо это проверить.

    В GdiProcessSetup выше тех строк, что я привел, в EDX должен быть кем-то загружен указатель на Х-структуру: Ага, выше вот что:
LEA    EDX, [EAX+EDX+000001DC]

    Чуть не запутался. Обратите внимание, такая форма записи означает, что в EDX будет скопирован адрес, который будет получен при [сложении составляющих] (в общем, так же, как LEA EDI,[EDX+50] :).
    В EDX в этом случае был ноль, а в EAX - адрес, не очень далекий от того, который меня интересует.
    Теперь надо заглянуть, где выше изменился EAX.
MOV    EAX,FS:[00000018]      ; FS - это  сегментный регистр. Интересно, что там?
MOV    EDX, [EAX+000001D8]    ; EDX становится нулем, потому что EAX+1D8 - это
                              ; указатель на пустое двойное слово (в tut5 в этот раз)

    Я уже и забыл, что в пространстве памяти нужно адресовать не только смещение.
    После уроков Калашникова, а это было восемь месяцев назад, не видел такой записи (сегментный регистр : смещение). Чтобы врубиться, что означает FS в этой строке, мне понадобилось полчаса :).
    Во-первых, нужно заметить что в отличие от DOS (без расширителей защитного режима) форточки в сегментных регистрах хранят не адрес сегмента, а  селектор (читай про модели памяти в архитектуре Intel). Во-вторых, регистр FS, как написано во всяких "умных" источниках, используется внутренне виндами. Ну и, в-третьих, я посмотрел и убедился, что эта строчка (с FS:[18]) не частный случай. То есть она же присутствует в других API-функциях. Это как минимум означает, что FS указывает на что-то "постоянное". В документации от SoftIce'a написано, например:
mov eax, FS:[124] ; получить текущий поток (KTEB) get the current thread.
    А еще в той же доке есть раздел про команду GDT (Global Descriptor Table) Глобальная таблица дескрипторов (читай про модели памяти в архитектуре Intel)
    Мой перевод:

    В Windows NT сплошная (flat) 32-битная архитектура. Несмотря на то, что ей по-прежнему нужно использовать селекторы, она использует их минимально. Большинство Win32 приложений и драйверов даже не подозревают о существовании селекторов. Ниже следующие сокращения получены  командой SoftICE'a "GDT", которая показывает селекторы в глобальной таблице дескрипторов.
GDTbase=80036000  Limit=03FF

0008  Code32  Base=00000000  Lim=FFFFFFFF  DPL=0  P  RE
0010  Data32  Base=00000000  Lim=FFFFFFFF  DPL=0  P  RW
001B  Code32  Base=00000000  Lim=FFFFFFFF  DPL=3  P  RE
0023  Data32  Base=00000000  Lim=FFFFFFFF  DPL=3  P  RW
0028  TSS32   Base=8000B000  Lim=000020AB  DPL=0  P  B

0030  Data32  Base=FFDFF000  Lim=00001FFF  DPL=0  P  RW
003B  Data32  Base=7FFDE000  Lim=00000FFF  DPL=3  P  RW
0043  Data16  Base=00000400  Lim=0000FFFF  DPL=3  P  RW
0048  LDT     Base=E156C000  Lim=0000FFEF  DPL=0  P
0050  TSS32   Base=80143FE0  Lim=00000068  DPL=0  P
0058  TSS32   Base=80144048  Lim=00000068  DPL=0  P

    Назначение первых четырех селекторов - адресовать сплошное 4GB линейное адресное пространство. Это flat-селекторы, которые используют Win32 приложения и драйверы.
    Первые два селектора имеют DPL ноль (читай про модели памяти в архитектуре Intel) и используются драйверами устройств и системными компонентами для адресации системного кода, данных и стеков. Селекторы 1B и 23 предназначены для Win32 приложений и адресуют пользовательский уровень кода, данных и стеков. Эти селекторы имеют неизменные значения, и системный код Windows NT часто обращается к ним, используя их буквенные названия.

    Селектор со значением 30h адресует Kernel Processor Control Region, и он всегда расположен по базовому адресу 0xFFDFF000. Когда запускается системный код, этот селектор сохраняется в сегментном регистре FS. В числе многих других вещей Processor Control Region содержит current kernel mode exception frame по смещению 0 (понятия не имею, что это значит).

    Точно так же селектор со значением 3Bh - это селектор уровня пользователя, он адресует блок среды текущего user-потока (current user thread environment block - UTEB). Этот селектор сохраняется в сегментном регистре FS, когда запущен код уровня пользователя, и его current user-mode exception frame "находится" по смещению 0 (опять не знаю, чего пишу).
    Базовый адрес этого селектора меняется в зависимости от того, какой user-mode поток исполняется.
    Когда происходит переключение потоков, базовый адрес начала этого GDT селектора обновляется, чтобы отображать текущий  UTEB.
...
    Как-то так.
    А вы думаете, я понял? :) Я только перевел и то не всё. Потом прочитал это пару раз и опять не понял. :/
    Пойду, чай налью, подумаю, может, что и дойдет.
    Чай вкусный сегодня! Жалко, что шоколада нету, зато есть орешки и мед, тоже для мозгов полезно.
    Прочитал все, что я тут накатал по этому вопросу от начала до тупика.
    Чё-то я разбежался в одном вопросе всю винду расковырять. Эдак и устать недолго. Надо эту чтиву закруглять.
    Так, вернусь к баранам. Фокус в том, что в сегментном регистре FS с самого начала проги лежит 0038. Но это, похоже, неважно, мне кажется, что 38 - то же самое, что 3B. Например, на команду "GDT 38" айс просто выдает данные о 3B. И команды
    "dd 38: любое смещение" и
    "dd 3B: то же смещение" на экран выводят одинаковое содержимое памяти.
    Получается, что при выполнении API-функций, вызванных приложением, в регистре FS не 30h, что указывало бы на Kernel Processor Control Region, а 38 "равно" 3B, что указывает на UTEB.
    Так и должно быть, потому что API-функции - это вовсе не системная область (читай про модели памяти в архитектуре Intel :). Вот после опкода SYSENTER мы попадаем в зону LDP 0, и там регистр FS будет изменен примерно вот так:
MOV   EBX,00000030
MOV   FS,BX

Теперь проверьте свою память. О чем был вопрос? Только не подглядывайте... Я так и не вспомнил. Посмотрел и ужаснулся: насколько я далек от Q00C. Кстати, одна из причин, по которой я веду дневник, - запутанность темы (или моя способность все так запутать :). Если это все хоть как-то не записать, то к середине вопроса забудешь, чего хотел. Итак, "Рабыня Изаура", краткое содержание предыдущих серий:
    Рисует ли TextOut строку?
    TextOut практически "ничего" не делает
    она вызывает GdiProcessSetup
    Та копирует строку в некий массив или структуру, которую я обозвал X-структура+50.
    Что такое X-структура?

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

Из того, что я чуть выше написал, при некотором напряжении мозгов можно сообразить, что:
    в UTEB (FS:[18h]) берется адрес,
    к этому адресу прибавляется 1DC ([EAX+EDX+1DC]) и записывается в EDX,
    потом к нему +50h и вот там находится конечный адрес в X-структуре, куда записывается строка текста. А в верхние 50h байт пишутся параметры вывода.

    Все, дальше без эксперимента не обойтись. Запущу tut5_d (тот, что с двумя строками на разный WM_PAINT) и сравню содержимое памяти по адресу, на который, после вышеизложенных строк, указывает EDX. Только перед обновлением X-структуры хорошо бы ее обнулить.
    Ставлю бряк на обе строки call TextOut в tut5_d. Захожу внутрь первой (с красивым текстом) и в GdiProcessSetup после строки
LEA   EDX, [EAX+EDX+000001DC]

команда "dd EDX"
Забиваю нули в память (примерно 320 штук :).
Потом в айсе заполняется эта X-структура.
Сохраняю экран (см. сноску 1)
    Повторяю действия во второй отрисовке.
    Сразу же замечу - много нулевых полей вовсе не означает, что все они не обновлялись.
    Во второй раз я забил 320 "А", сопоставил и узнал все неиспользуемые поля на примере tut5_d & tut5.
    Потом еще покрутил-повертел, ну точно, как контекст! Смотрите:
Tut5
                  1           2           3           4

A   7FFDE1DC   00020098    0032C8C8    00FF0000    00000002

              Цвет Шрифта  Цвет фона   X,Y-координаты строки
B   7FFDE1EC   0032C8C8    00FF0000    00000000    00000000

C   7FFDE1FC   80000000    AAAAAAAA    AAAAAAAA    AAAAAAAA

                                      Длина строки
D   7FFDE20C   AAAAAAAA    00000000    00000022    00000000

                Шрифт
E   7FFDE21C   DF0A0C48    00000000    00000000    00000000

           А дальше пошла строка: "W.i.n.3.2..."
F   7FFDE22C   00690057    0033006E    00200032    00730061
G   7FFDE23C   00650073    0062006D    0079006C    00690020
H   7FFDE24C   00200073    00720067    00610065    00200074
I   7FFDE25C   006E0061    00200064    00610065    00790073

Последние символы "!00h"              
J   7FFDE26C   00000021    AAAAAAAA    00060008    018A0021

(В tut5d все поля, так или иначе, обновляются.)
    Это контекст.
    Но контекст устройства, насколько я понимаю, связан с устройством, а здесь устройством и не пахнет.
    Когда tut5 стартует, создается его виртуальное адресное пространство, и в нем есть показанные адреса (у меня - 7FFDE1DC-7FFDE26C), изначально там нули, по ходу выполнения туда забиваются необходимые для отрисовки GUI данные, но только в tut5. В tut5_d, например, будет другое виртуальное пространство, и там будут другие процессы. Таким образом, до сообщения WM_PAINT для данного окна значения X-контекста ни на чем не отражаются.
    Этот контекст - местного значения. Может быть, это и есть контекст отображения, но этого я узнать не могу, на нем не написано :).
    Теперь фраза Iczelion'a: "Для видеодисплея контекст устройства обычно сопоставлен определенному окну на экране" обретает смысл.
    Всё!

    К сожалению, эта чтива менее полезна, чем мне бы хотелось, и к тому же она получилась слишком длинная.
    Обещаю в дальнейшем быть более лаконичным и больше не зарываться в особенности конкретной версии форточек, потому как дело это недолговечное. С другой стороны, я доказал сам себе, что и без полноценной доки можно многое раскопать, а при большом желании даже больше, чем я думал. "Не так все это страшно, как кажется из самолета", - сказал паренек, прыгая без парашюта :).

    Еще я не написал, что по совету MozgC (читай "Огромный FAQ по взлому программ") по ходу дела расковырял 8 крякмов от FaNt0m'a, чего и вам желаю. Дело это интересное и очень полезное, особенно N4 (хе-хе :)). Дам пару советов: не зацикливайтесь, если что-то не получается, возьмите следующий crackme. А вообще, если вы с интересом прочитали все три чтивы, то эти крякми у вас должны пройти без вопросов на форумах и чтения статей.
    После вскрытия и решения фантомовских крякмов у вас может закружиться голова и возникнуть ощущение, будто вы - Neo (по себе знаю :). Чтоб вернуться на землю, холодного душа будет мало, мне помогла книжка:
    А. Ахо, Дж. Ульман
    Теория синтаксического анализа, перевода и компиляции
    Том 1: синтаксический анализ
    Издательство "МИР"
    Москва, 1978

    Просмотрев несколько страниц, я осознал свое место в этом мире :).
    Если этой книги нет, можно взять трехтомник Кнута и просмотреть по-быстрому все 2400 стр. Очень, знаете ли, пользительно для вразумления :).

    Но, с другой стороны, ведь мы только начали!

    Думаю, не раньше чем через месяц напишу продолжение, пока не знаю о чем: уроки дальше разбирать или отвлечься на одну статью, исследовать какую-нибудь простую защиту на примере коммерческой программы :). Может, вы подскажете?

    C полезными мыслями обращайся к Bitfry на ящик bitfry@land.ru


Сноски:

1. Забыл сказать, что в WinXP можно поставить IceExt (плагин к айсу), в нем полезные фишки, одна из них возможность дампить экран в почти текстовый файл (текст с атрибутами цвета). Текст из этого файла я получаю в WinHex (то, что под руку попалось). Там есть возможность написать скрипт типа:
Goto 0x00
{Move 1
Write 0x00}[13118]  // цикл замены цветового байта на 00h.
                   // [xxxx] - всего символов в строке * количество строк -2
ReplaceAll 0x00 0x // удалить все 00h
Goto 0x00
{Move 159          // Move
Write 0x0D}[82]    // цикл разбивки на строки. [xx] - количество строк
Save               // сохранить файл поверх старого

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

От автора: Для полного понимания также прочитайте Дневники чайника. Чтива 0, виток 0 + http://bitfry.narod.ru/err.htm (исправления).



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


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

Bitfry - Автор 24.02.2005 08:54:17
Если кому-то нужно продолжение напишите мне письмо. Спустя 4 месяца я уже сомневаюсь, что подобные статьи хоть кому-то нужны. :( Буду ждать писем с идеями о чём дальше писать. Если писем не будет, продолжения тоже не будет.
---
neyron 04.04.2005 13:28:47
Я оба дневника читал, вроде ниче нормально.
Возьми прогу какуюнить распакуй, напиши кейген, и все ето в дневник 4
---
Bitfry - Автор 18.04.2005 21:10:09
Так и сделал. Только получилась отдельная статья. А дневник я продолжу Чтивой 0 (уже почти написал, скоро будет).
---
нет_его 11.05.2005 05:13:29
Пиши пиши еще....очень прикрасные статьи.просто великолепно. Спас за это
---
Exelios 04.07.2005 15:13:08
Твои статьи мне очень помагают в изучении ассма.
---
Bitfry - Автор 05.07.2005 15:28:38
Вот-вот выйдет Чтива 0 виток0 (неделя-две). Для Асма самое то. 10 уроков - шаг за шагом, от нуля до Чтивы I. А это (I,II,III) не Асм! Здесь больше WinAPI, да и то кашей. Буду работать над Ассемблером как таковым.
За отзывы спасибо. Пишу, и питаюсь ими :).
---
Автор 15.08.2005 05:05:30
Чтива 0 виток0 и даже одна глава из витка1 готовы.
Читать всем новичкам обязательно!
Дневники I,II,III скорее всего будут подправлены. Здесь довольно много лишнего и слишком много ошибок.
Поэтому и читайте Чтиву0.
---
SQR 09.10.2005 02:34:47
Дайте чё по сложней
---

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



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


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