MDI-приложение для x64

Тема в разделе "WASM.X64", создана пользователем ml64, 29 окт 2017.

Метки:
  1. ml64

    ml64 New Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    23
    Здравствуйте, уважаемые дZенствующие!
    Прошу понять и простить, но несколько месяцев поиска, чтения и осмысления документации, примеров и форумов, а также тупого перебора вариантов не принесли результата. Поэтому пишу сюда.
    Нужно создать простейшее, тривиальное MDI-приложение для платформы win64.
    Двигаюсь двумя путями - имею две группы проблем:
    1) MASM не создаёт окно фрейма, хотя после CreateWindowExA rax != 0, но GetLastError возвращает success;
    2) fasm создаёт окно фрейма, но я не могу понять, как передаются параметры в процедуру ChildProc. Окно фрейма рисуется, меню фрейма работает прекрасно, продедура ChildProc реагирует на команды меню, но параметров не видно ни в регистрах, ни в стеке, окно Child не рисуется.
    Код прилагаю...
    Помогите разобраться в дебрях...
    Добра Вам!
     
    Последнее редактирование: 29 окт 2017
  2. unc1e

    unc1e Active Member

    Публикаций:
    2
    Регистрация:
    28 июл 2017
    Сообщения:
    287
    https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms632680(v=vs.85).aspx
    GetLastError должен возвращать ERROR_SUCCESS при rax != 0
     
  3. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.786
  4. ml64

    ml64 New Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    23
    Уважаемый Mikl__ !

    Спасибо Вам за Вашу работу и за персональную помощь! Благодаря Вам всё получилось!

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

    Вложения:

  5. ml64

    ml64 New Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    23
    А вот ещё простое MDI-приложение: перевод на fasm для Win64 примера из учебника Чарльза Петцольда.
    Несколько упрощённое (без массива цветов и рандомного механизма), т.к. основная цель - исследовать работу MDI.
     

    Вложения:

  6. ml64

    ml64 New Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    23
    Перевод указанного примера Петцольда на MASM/WASM для win64 зашёл в тупик.
    Причина следующая: полное непонимание требования о выравнивании стека.
    1. Дизассемблирование собранной Fasm программы показывает, что sub rsp, 8 - выравнивание стека с учётом адреса возврата -
    подставляется ассемблером в начале процедур WinMain и ChildWndProc.
    В конце тех процедур, где было выравнивание, стек не восстанавливается. Команда add rsp, 8 не встречается нигде.
    При этом в процедурах FrameWndProc и CloseEnumProc стек не выравнивается ни в начале, ни в конце.
    1.1. В чём логика выравнивания?
    1.2. Почему пользуются вычитанием sub rsp, 8, а не универсальным and rsp, -16?
    2. Зачем макрос invoke создаёт буфер для каждого вызова функции?
    Нельзя ли один раз в начале процедуры сделать sub rsp, 20h или даже 60h, или сразу 80h, а в конце восстановить add rsp, 80h?
    Уж слишком часто между соседними вызовами call стоят бессмысленные add rsp, 20h sub rsp, 20h...
     
  7. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.786
    ml64,
    так реализован МАКРОС invoke в FASM, не нравится ― перепиши тело макроса, который внутри твоей процедуры отыскивал бы вызов WinAPI с максимальным количеством параметров и подставлял бы в качестве эпилога push rbp/mov rbp,rsp/sub rsp,XX а в конце leave, а не выставлял бы вокруг каждого вызова WinAPI "бессмысленные add rsp,20h и sub rsp,20h"
    а hiew32 говорит. что выравнивание есть
    начало FrameWndProc
    Код (ASM):
    1. 402453: push rbp <-- выравнивание push reg = sub rsp,8/mov [rsp],reg
    2. mov rbp,rsp
    3. sub rsp,8 <-- эта строка нужна, так как в стек отправляется содержимое нечетного числа регистров
    4. push rbx
    5. push rsi
    6. push rdi
    конец FrameWndProc
    Код (ASM):
    1. 40291F: pop rdi
    2. pop rsi
    3. pop rbx
    4. leave <-- возвращаем содержимое rsp в исходное состояние
    5. retn
    Вспомнил! В FASM есть макросы frame/endf которые тебе нужны. Пример использования есть в fasm\examples\win64\opengl
     
    ml64 нравится это.
  8. ml64

    ml64 New Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    23
    Попробую сделать проще: аналог invoke, который загружает параметры в регистры, но не меняет rsp.
    Спасибо! Ну конечно!!! Вот решение моей проблемы:
    Если процедура имеет параметры, например:
    WndProc proc hWnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD
    то при ассемблировании MASM вставляет строки:
    push rbp
    mov rbp,rsp

    ...и тогда выравнивание не требуется.
    Но если процедура не имеет параметров: WndProc proc
    то при ассемблировании MASM ничего не добавляет, и требуется выравнивание:
    sub rsp, 8
    ...
    add rsp, 8
     
  9. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    3.786
    попробуй, только не забывай, что в регистры rcx, rdx, r8, r9 загружаются первые 4 параметра, а что делать если параметров больше 4? Например, как будет выглядеть invoke для CreateFile или для CreateWindowEx?
     
  10. ml64

    ml64 New Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    23
    Для таких функций буду использовать стандартный invoke
    А чтобы не считать байты стека в прологе и эпилоге:
    Код (ASM):
    1. mov rax,rsp
    2. and rax,0Fh
    3. sub rsp,rax
    4. mov StackMask,rax
    5. ...
    6. add rsp,StackMask
     
    Последнее редактирование: 26 ноя 2017
  11. ml64

    ml64 New Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    23
    Всё запустилось, работает стабильно. На все вопросы топика ответы получены.
    Ещё раз спасибо, Mikl__!
    Применённые приёмы обусловлены следующими ссображениями:
    1. На адрес вершины стека влияют: адрес возврата (8 байт), сохранённые регистры, параметры процедуры, локальные переменные. Их размеры различны, но ассемблер всё равно выделяет память квантами, равными 8 байтам (проверял, но могу ошибаться: для локальной переменной типа BYTE ассемблер выделил 8 байт). Выделение памяти происходит в самом начале процедуры.
    2. Перед вызовом API-функций стек дожен быть выровнен по 10h. Можно вычислять адрес вершины стека, а можно просто обнулить младший байт: and rsp,0Fh. Никакие данные от этого не потеряются, т.к. стек растёт вниз.
    3. Не нужно запоминать, на сколько байт был смещён стек при выравнивании, т.к. ассемблер в прологе процедуры помещает инструкции push rbp mov rbp,rsp, а в эпилоге - инструкцию leave, т.е. при выходе из процедуры стек всё равно вернётся в исходное состояние.
    4. Также перед вызовом API-функций необходимо зарезервировать 20h байт в стеке для возвращаемых параметров.
    5. Перед вызовом каждой функции этого делать не обязательно, т.к. вызовы функций происходят очень часто, и частое перемещение вершины стека нерационально. Достаточно один раз зарезервировать 80h байт в стеке в прологе процедуры. Этого хватит на 16 параметров по 8 байт
     

    Вложения:

    Последнее редактирование: 28 ноя 2017
  12. ml64

    ml64 New Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    23
    Здесь досадная очепятка. Обнулить младший байт: and rsp, -16 (или, что то же самое, and rsp, 0FFFF FFFF FFFF FFF0h)
    В остальном указанные пять пунктов вроде как работоспособны. За две недели не подводили.
    Сделал. Работает. Но не один макрос, а пять: call1, call2, ... call5 - по числу параметров. Так проще для машины и удобнее для меня.
    Код (кому интересно) прилагаю
     

    Вложения:

  13. ml64

    ml64 New Member

    Публикаций:
    0
    Регистрация:
    29 окт 2017
    Сообщения:
    23
    На всякий случай похвастаюсь - может, кому пригодится.
    Работающий перевод 18 урока из книги Чарльза Петзольда на fasm.
    Что удалось выяснить:
    1) переменная static - это глобальная переменная!
    Только этой глобальной переменной пользуется всего одна процедура;
    Иными словами, она служит для хранения данных, общих для всех окон одного класса.
    2) для хранения индивидуальных данных каждого окна
    выделяется heap, который привязан к дескриптору этого окна.
    3) чтобы получить случайное число в диапазоне [0, x], надо:
    сгенерировать случайное число (на Ivy Bridge и новее есть инструкция RdRand),
    разделить полученное число на x и взять остаток.
    Инструкция div помещает остаток от деления в регистр rdx.
     

    Вложения:

    Последнее редактирование: 29 сен 2019