NASM - The Netwide Assembler

NASM Forum => Example Code => Topic started by: Max on June 23, 2014, 07:05:33 AM

Title: Win32 Message Box
Post by: Max on June 23, 2014, 07:05:33 AM
Hello, I'm new here, and to Assembly Language in general.
I thought I would share my first program, a very simple one.

Code: [Select]
; File: Win32MessageBox.asm
; Version: 1.2
; Author: Max
; Date: 6/23/2014
;
; --------------------------------------------------------
; - A very simple Win32API call to create a message box. -
; --------------------------------------------------------
;
; Compile with:
; nasm -fwin32 Win32MessageBox.asm
; gcc Win32MessageBox.obj -o Win32MessageBox.exe
;
; C-Style Code
; **
; * #include <windows.h>
; *
; * int WINAPI
; * WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow)
; * {
; *     MessageBox
; *     (
; *         NULL,
; *         "Text!",
; *         "Title",
; *         MB_ICONINFORMATION | MB_OK
; *     );
; *
; *     return 0;
; * }
; **

[BITS 32]

EXTERN _MessageBoxA@16

SEGMENT .DATA

paramText   DB  "Text!", 0
paramTitle  DB  "Title", 0

SEGMENT .TEXT
    GLOBAL _WinMain@16
_WinMain@16:
    PUSH    EBP
    MOV     EBP,    ESP
   
    ; http://msdn.microsoft.com/en-us/library/ms645505.aspx
    ;       0x00000040 = MB_ICONINFORMATION
    ; OR    0x00000000 = MB_OK
    ; =====================================
    ;       0x00000040
    PUSH    0x40
    PUSH    paramTitle
    PUSH    paramText
    PUSH    0x0
   
    CALL    _MessageBoxA@16
   
    XOR     EAX,    EAX
    LEAVE
   
    ; _WinMain@16 follows __stdcall, so
    ; 16 bytes of parameters (see mangled name) must
    ; be popped
    RET     0x10
Title: Re: Win32 Message Box
Post by: gammac on June 23, 2014, 08:21:36 AM
Hi Max,

I've found a mistake.

   
Code: [Select]
RET WM_QUIT
'ret WM_QUIT' pops 12 bytes from the stack and then returns. That's all and that's dangerous, because the stack holds important things, such as return addresses and you pops it away. ;)

If you want to return the value 23, you have to move that value into the eax (32 bit code) register.

If you write a windows gui application you'll need a WindowProc callback funktion and that function will receive the windows message WM_QUIT.

EDIT: sorry, "The WM_QUIT message is not associated with a window and therefore will never be received through a window's window procedure." msdn
Title: Re: Win32 Message Box
Post by: Max on June 23, 2014, 02:33:19 PM
I've edited the code as suggested. You are right, for some reason I was thinking the return value is specified by RET (whoops).
Title: Re: Win32 Message Box
Post by: gammac on June 23, 2014, 04:00:29 PM

Code: [Select]
   
    MOV     EAX,    WM_QUIT
    LEAVE
   
    ; _WinMain@16 follows __stdcall, so
    ; 16 bytes of parameters (see mangled name) must
    ; be popped
    RET     0x40

0x40 isn't 16 ;)

ok, thats not the right use of WM_QUIT. Read about windows message loops.
The best way to quit your windows application is to call the API function ExitProcess, e.g. like this:

Code: [Select]
xor eax, eax     ; zero to indicate everything went well
push eax
call ExitProcess

And another point is, windows won't call your entry point with these 4 arguments (HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow) on the stack. Windows puts nothing on the stack that you have to pop from it. Therefore, you mustn't restore the stack.

If you need the hInst argument you can call the API function GetModuleHandle, the API function GetCommandLine gives you a pointer to the command line.

Best wishes


EDIT: I'd like to think that gcc acts as a linker and that it does nothing else then linking.
Title: Re: Win32 Message Box
Post by: Max on June 24, 2014, 04:25:13 AM
Quote
RET     0x40
    Whoops. I meant 0x10.



    I'm now looking into the deep, dark depths of MSDN instead of just skimming the code boxes.



    I read some about message loops and from what I understand it is a queue of messages being sent off to be processed.
    Fortunately for this simple message box (not even a window), there is no message loop at all (or at least it is abstracted away within the message box). As such there won't be a WM_DESTROY (http://msdn.microsoft.com/en-us/library/windows/desktop/ms632620.aspx) message to process, nor an exit code to pass on. According to your code snippet and MSDN's page on WinMain (http://msdn.microsoft.com/en-us/library/windows/desktop/ms633559.aspx):
Quote
If the function terminates before entering the message loop, it should return zero.



...

And another point is, windows won't call your entry point with these 4 arguments (HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow) on the stack. Windows puts nothing on the stack that you have to pop from it. Therefore, you mustn't restore the stack.

If you need the hInst argument you can call the API function GetModuleHandle, the API function GetCommandLine gives you a pointer to the command line.

...
    This part confuses me. I really thought Windows sets up the stack with those four parameters, to follow __stdcall (http://msdn.microsoft.com/en-us/library/zxk0tw93.aspx), which specifically requires the "callee" to clean up the stack. A quick implementation of something I found on StackOverflow (http://stackoverflow.com/questions/5951081/assembler-getting-win32s-winmain-on-stack-parameters) accurately grabs the lpCmdLine parameter.
Code: [Select]
; Compile with:
; nasm -fwin32 MessageBoxTest.asm
; gcc MessageBoxTest.obj -o MessageBoxTest.exe

[BITS 32]

EXTERN _MessageBoxA@16

SEGMENT .TEXT
    GLOBAL _WinMain@16
_WinMain@16:
    PUSH    EBP
    MOV     EBP,    ESP
   
    MOV     EAX,    [EBP + 16]
    PUSH    0x0
    PUSH    EAX
    PUSH    EAX
    PUSH    0x0
   
    CALL    _MessageBoxA@16
   
    XOR     EAX,    EAX
    LEAVE
    RET     0x10



    The relationship between Windows and my "process" is also confusing to me. I've read MSDN's article on Terminating a Process (http://msdn.microsoft.com/en-us/library/windows/desktop/ms686722.aspx), ExitProcess (http://msdn.microsoft.com/en-us/library/windows/desktop/ms682658.aspx), and the general article About Processes and Threads (http://msdn.microsoft.com/en-us/library/windows/desktop/ms681917.aspx). If I read correctly, threads all have their own context: a virtual (user?) stack, registers, etc. while each program has at least 1 thread (mine have 1). This is to protect other programs, and prevent system failure, right? I have to wonder what I was popping off when I RET 0x40'd, shouldn't the (user?) stack have some sort of underflow error? If Windows doesn't initialize the stack with those parameters from WinMain, what is put on the (user?) stack?
    The other question on my mind: MSDN says a process will terminate if "[t]he last thread of the process terminates", so if a program is single-threaded and devoid of DLLs what is the advantage of using ExitProcess?
Title: Re: Win32 Message Box
Post by: gammac on June 24, 2014, 09:23:46 AM

...Fortunately for this simple message box (not even a window), there is no message loop at all ...

Yes, you are right. I thought I push you in the right direction where WM_QUIT message comes in action, because you tried to use it in a wrong manner.

A quick implementation of something I found on StackOverflow (http://stackoverflow.com/questions/5951081/assembler-getting-win32s-winmain-on-stack-parameters)accurately grabs the lpCmdLine parameter.

Do you relly cross-refer to that stuff as serious and accurate programming style? I wouldn't and the poster was asking what's wrong!

If Windows doesn't initialize the stack with those parameters from WinMain, what is put on the (user?) stack?

I think windows put something there, but it's undocumented and therefore you shouldn't try to use it.

I think, if you really want to have a c++ like WinMain function you were on the right and save side if you would using some code that looks like this:

Code: [Select]
;
; WinMain.asm
; 06/23/2014
;
; Assembler: NASM version 2.11.05 compiled on May 21 2014
; Linker: GoLink.Exe Version 1.0.0.0
;
; nasm -fwin32 -oWinMain.obj WinMain.asm
; golink /entry start /fo WinMain.exe WinMain.obj kernel32.dll
;

cpu 386

global start

extern GetCommandLineW
extern GetModuleHandleW
extern ExitProcess




[SECTION .code use32]

start:

push 3 ; nCmdShow SW_MAXIMIZE   who needs this argument????????

call GetCommandLineW ; win32 API

push eax ; lpCmdLine

xor eax, eax
push eax ; hPrev   nobody need this argument.

push eax
call GetModuleHandleW; win32 API

push eax ; hInst

call WinMain ; call WinMain with 4 arguments on the stack

exit:

push eax ; error code
call ExitProcess ; win32 API




; WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow)

WinMain:

; do some useful things here

xor eax, eax ; return 0, everything went well

ret 16 ; restore the stack, pop 4 arguments

    The other question on my mind: MSDN says a process will terminate if "[t]he last thread of the process terminates", so if a program is single-threaded and devoid of DLLs what is the advantage of using ExitProcess?

Maybe nothing, but you are doing it right, if you call brave this ExitProcess API.

Try to implement some more things, I am looking forward to your code samples.