Страница 1 из 1

Новичек: проблема с программой под Win32

Добавлено: 25 авг 2005, 02:24
sawych
Написал сюда, потому что мне нужна помощь, у меня куча вопросов.
Очень надеюсь на ваше понимание и помощь, и пожалуйста не отсылайте меня к мануалу как это делают на некоторых форумах, если вы сами в силах мне помочь.
Начну с самого главного:
Я сразу скажу что в программировании на Ассемблере я новичек Вообщем освоил я основы (вроде), правда есть некоторые вопросы из-за дурацких высокоуровневых директив. Вот начал изучение Asm под Win32 и начал я с уроков Iczelion`на. И вот на 5 уроке у меня возникла проблема.
Дело в том что я не использую директив ветвления (не хочу), а пользуюсь стандартными средствами Ассемблера. Вот переписал программу из 5 урока и запустил.
Программа выводит текст и тут же вызывает ошибку (которую мелкософт предлагает оправить). Смотрел в отладчике, ошибка вроде заключается в том что программа пытается вернутся (ret) от куда-то на адрес 00000000 (то есть ошибка из-за стэка?).
Причем если переменную для временного сохранения хэндла логического шрифта сделать не локальной то есть не в стэке, а поместить в секции .data?, то все работает как надо. Пожалуйста, кто нибудь может объяснить чайнику почему так происходит?
Вот исходник:

Код: Выделить всё

.386
.model flat,stdcall


include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib

RGB macro red,green,blue
        xor eax,eax
        mov ah,blue
        shl eax,8
        mov ah,green
        mov al,red
endm


.DATA

class_name         db      "win_class",0
window_name        db      "Title",0
font_name          db      "script",0
out_string         db      "Win32 assembly is great and easy!",0
; структура, описывающая класс окна.
wc WNDCLASSEX <4*12,CS_HREDRAW or CS_VREDRAW,offset win_proc,0,0,?,?,?,\
                         COLOR_WINDOW+1,0,offset class_name,0>
                         
.DATA?

msg_ MSG <>        ; структура, в которой возвращается
                   ; сообщение после GetMessage
ps PAINTSTRUCT <>  ; структура для граф. функций

.CODE

_start:
        xor        ebx,ebx          ; в EBX будет 0 для команд push 0
                                    ; (короче в 2 раза)
; определим идентификатор нашей программы
        push       ebx
        call       GetModuleHandle
        mov        esi,eax          ; и сохраним его в ESI
; заполним и зарегестрируем класс
        mov        dword ptr wc.hInstance,eax ; идентификатор предка
; выберем иконку
        push       IDI_APPLICATION  ; стандартная иконка приложения
        push       ebx              ; идентификатор модуля с иконкой
        call       LoadIcon
        mov        wc.hIcon,eax     ; идентификатор иконки для нашего класса
; выберем форму курсора
        push       IDC_ARROW        ; стандартная стрелка
        push       ebx              ; идентификатор модуля с курсором
        call       LoadCursor
        mov        wc.hCursor,eax   ; идентификатор курсора для нашего класса
        push       offset wc
        call       RegisterClassEx  ; зарегистрируем класс
; создадим окно
        mov        ecx,CW_USEDEFAULT    ; push ecx короче push N в пять раз
        push       ebx              ; адрес структуры CREATESTRUCT (здесь NULL)
        push       esi              ; идентификатор процесса, который будет получать
                                    ; сообщения от окна (то есть, наш)
        push       ebx              ; идентификатор меню или окна-потомка
        push       ebx              ; идентификатор окна-предка
        push       ecx              ; высота (CW_USEDEFAULT - по умолчанию)
        push       ecx              ; ширина (по умолчанию)
        push       ecx              ; y-координата (по умолчанию)
        push       ecx              ; x-координата (по умолчанию)
        push       WS_OVERLAPPEDWINDOW     ; стиль окна
        push       offset window_name      ; заголовок окна
        push       offset class_name       ; любой зарегистрированный класс
        push       ebx              ; дополнительный стиль
        call       CreateWindowEx   ; создать окно (eax - идентификатор окна)
        push       eax              ; идентификатор для UpdateWindow
        push       SW_SHOWNORMAL    ; тип показа для для ShowWindow
        push       eax              ; идентификатор для ShowWindow
; больше идентификатор окна нам не потребуется
        call       ShowWindow       ; показать окно
        call       UpdateWindow     ; и послать ему сообщение WM_PAINT

; основной цикл - проверка сообщений от окна и выход по WM_QUIT
        mov        edi,offset msg_  ; push edi короче push N в 5 раз
message_loop:
        push       ebx              ; последнее сообщение
        push       ebx              ; первое сообщение
        push       ebx              ; идентификатор окна (0 - любое наше окно)
        push       edi              ; адрес структуры MSG
        call       GetMessage       ; получить сообщение от окна с ожиданием
                                    ; - не забывайте использовать PeekMessage
                                    ; если нужно в этом цикле что-то выполнять
        test       eax,eax          ; если получено WM_QUIT
        jz         exit_msg_loop    ; выйти
        push       edi              ; иначе - преобразовать сообщения типа
        call       TranslateMessage ; WM_KEYUP в сообщения типа WM_CHAR
        push       edi
        call       DispatchMessage  ; и послать их процедуре окна (иначе его просто
                                    ; нельзя будет закрыть)
        jmp        message_loop    ; продолжить цикл
exit_msg_loop:
; выход из программы
        push       ebx
        call       ExitProcess
;////////////////////////////////////////////////////////////////////////
; процедура win_proc
; процедура не должна изменять регистры EBP,EDI,ESI и EBX
;
win_proc proc
; так как мы получаем параметры в стеке, построим стековый кадр
        push       ebp
        mov        ebp,esp

        push       ebx ; функция должна сохр. этот регистр,
                       ; а мы его используем поэтому сохраняем его
; процедура типа WindowProc вызывается со следующими параметрами
        hWnd    equ dword ptr [ebp+08h]  ; идентификатор окна
        uMsg    equ dword ptr [ebp+0Ch]  ; номер сообщения (код сообщеиия)
        wParam  equ dword ptr [ebp+10h]  ; первый параметр
        lParam  equ dword ptr [ebp+14h]  ; второй параметр
; локальные переменные
        sub        esp,8 ; 2 X dword
        hdc     equ dword ptr [ebp+18h] ; хэндл контекста устройства (для граф. функций)
        hfont   equ dword ptr [ebp+1Ch] ; переменная для сохранения (восст.) измененяемого шрифта
                                        ; вот если эту переменную объявит не тут а в .data?
                                        ; то все работает
; если мы получили сообщение WM_DESTROY (оно означает что окно уже удалили
; с экрана, нажав alt-F4 или кнопку в верхнем правом углу)
; то пошлем нам же сообщение WM_QUIT
        cmp        uMsg,WM_DESTROY
        jne        not_wm_destroy
        push       0                    ; код выхода
        call       PostQuitMessage      ; послать WM_QUIT
        jmp        end_wm_check   ; и выйти из процедуры
  not_wm_destroy:
        cmp        uMsg,WM_PAINT ; сообщение WM_PAINT ?
        jne        not_wm_paint  ; Нет - прыгаем дальше
  ; получим хэндл контекста устр-ва
        mov        ebx,offset ps ; push ebx быстрее чем push N
        push       ebx
        push       hWnd          ; хэндл окна в кот. будем рисовать
        call       BeginPaint          ; в eax хэндл контекста устр-ва
        mov        hdc, eax
  ; создадим логический шрифт и получим его хэндл
        mov        ebx,offset font_name ; push ebx быстрее чем push N
        push       ebx             ; смещение строки названия гарнитуры шрифта
        mov        ebx,DEFAULT_PITCH or FF_SCRIPT
        push       ebx             ; питч и семейство шрифта (объед. or' ом)
        push       DEFAULT_QUALITY ; качества вывода
        push       CLIP_DEFAULT_PRECIS  ; что делать с символами, котоpые
                                        ; вылезают за пpеделы отpисовочного pегиона
        push       OUT_DEFAULT_PRECIS   ; насколько должен близко должен
                                        ;пpиближаться фонт к хаpактеpистикам,
                                        ; котоpые мы указали
        push       OEM_CHARSET          ; символьный набоp фонта]
        mov        ebx,0                ; push ebx быстрее чем push N
        push       ebx                  ; признак ПЕРЕчеркнутости
        push       ebx                  ; признак ПОДчеркнутости
        push       ebx                  ; признак курсива
        push       400                  ; устанавливает толщину линии
                                        ; 400 - нормальная толщина
        push       ebx                  ; насколько символ должен быть повеpнут (0.1 deg)
        push       ebx                  ; указывает оpиентацию вывода следующего символа,
                                        ; относительно пpедыдущего
        push       16                   ; желаемая шиpина символов
        push       24                   ; желаемая высота символов
        call       CreateFont           ; eax = хэндл логического шрифта
  ;     выберем наш шрифт для контекста устр-ва
        push       eax                  ; хэндл логического шрифта
        push       hdc                  ; хэндл контекста устр-ва
        call       SelectObject
  ;     сохраним хэндл предидущего шрифта (он в eax)
        mov        hfont, eax
  ;     установим цвет шрифта
        RGB       200,200,50            ; цвет
        push       eax
        push       hdc
        call       SetTextColor         ; устанавливаем цвет текста
  ;     установим цвет фона
        RGB        0,0,255              ; цвет
        push       eax
        push       hdc
        call       SetBkColor           ; устанавливаем цвет фона
  ; нарисуем текст в окне
        push       SIZEOF out_string    ; длина строки для вывода
        push       offset out_string    ; смещение строки
        mov        ebx,0
        push       ebx                  ; Y координата
        push       ebx                  ; X координата
        push       hdc
        call       TextOut              ; нарисуем текст
  ; восстановим старый хэндл шрифта
        push       hfont                ; сохраненный хэндл старого шрифта
        push       hdc                  ; хэндл контекста устройства
        call       SelectObject
  ; освободим хэндл контекста устр-ва
        mov        ebx,offset ps ; push ebx быстрее чем push N
        push       ebx
        push       hWnd
        call       EndPaint
  not_wm_paint:
; если мы получили другое сообщение - вызовем его обработчик по умолчанию
        pop ebx ; мы его сохраняли
        leave                       ; восстановим ebp
        jmp        DefWindowProc    ; и вызовем DefWindowProc с нашими параметрами
  end_wm_check:
        pop ebx ; мы его сохраняли
        leave                       ; восстановим ebp
        ret        16               ; и вернемся сами, очистив стек от параметров
win_proc endp
;////////////////////////////////////////////////////////////////////////
        end        _start
И еще: пожалуйста, если кто может объсните можно ли делать так:
объявить процедуру не просто так вот:

Some_Proc proc
..
Some_Proc endp

а вот так:

Some_Proc proc param1 :D WORD, param2 :D WORD
..
Some_Proc endp

и обращаться к переданым параметрам не через стэк, а прямо по именам?
И надо ли после такого объявления указывать в ret кол-во удаляемых из стэка байт?
И зачем нужен прототип процедуры? Только для того чтобы ее вызывать через invoke
или это еще что-то дает, и почему когда используют высокоуровневые директивы
не пишут пролога и эпилога процедуры и не указывают кол-во удаляемых байт из стэка в ret?
Надеюсь на вашу помощь =)

Добавлено: 25 авг 2005, 23:34
Andy
sawych, если ты начал изучать АСМ под Win32, то следовало бы сначала разобрацца с TASM32. Это эти макросы и инвоуки скрывают от тебя суть дела. Ну да ладно.
Пожалуйста, кто нибудь может объяснить чайнику почему так происходит?
Во-первых не надо никогда считать себя чайником - привыкнешь :) Теперь почему так происходит. Не вдаваясь особо в код сразу бросается в глаза следующая ошибка в WNDPROC:

Код: Выделить всё

; локальные переменные 
        sub        esp,8 ; 2 X dword 
        hdc     equ dword ptr [ebp+18h] ; хэндл контекста устройства (для граф. функций) 
        hfont   equ dword ptr [ebp+1Ch]
EBP ты присвоил значение ESP. Параметры процедуры лежат соответственно выше ESP - их ты определил правильно. Далее ты поступил тоже правильно - зарезервировал место под локальные переменные (sub esp,8). Но при определении переменных ошибся! Теперь подумай, ты отнял от esp 8 байт - как обратиться к этой области памяти зная, что в ebp = esp на момент входа в процедуру? Вот как:

Код: Выделить всё

; локальные переменные 
        sub        esp,8 ; 2 X dword 
        hdc     equ dword ptr [ebp-8] ; (!) 
        hfont   equ dword ptr [ebp-12] ; (!) 
Т.о. по адресу [ebp] у тебя лежит твои старый запушенный ebp. По адресу [ebp - 4] лежит твой запушенный ebx. Далее ты отнял 8 байт и по адресу [ebp - 8] первая локальная переменная.
если кто может объсните можно ли делать так:
Можно. Но только в masm32.
И надо ли после такого объявления указывать в ret кол-во удаляемых из стэка байт?
Кажись masm32 вместо твоей ret автоматически подставляет ret xx.
И зачем нужен прототип процедуры? Только для того чтобы ее вызывать через invoke
Да, главным образом.
и почему когда используют высокоуровневые директивы
не пишут пролога и эпилога процедуры и не указывают кол-во удаляемых байт из стэка в ret?
Ответил выше.

Добавлено: 26 авг 2005, 01:28
sawych
2 Andy:

Большое спасибо, все разложил по полочкам. =)
Я понял свою ошибку. Теперь, если что еще какой вопрос у меня будет, то знаю к кому обратиться за ответом =)