Tutorial 14: Process

We will learn what a process is and how to create and terminate it.

Download the example here.

Preliminary:

What is a process? I quote this definition from Win32 API reference:
"A process is an executing application that consists of a private virtual address space, code, data, and other operating system resources, such as files, pipes, and synchronization objects that are visible to the process."
As you can see from the definition above, a process "owns" several objects: the address space, the executing module(s), and anything that the executing modules create or open. At the minimum, a process must consist of an executing module, a private address space and a thread. Every process must have at least one thread. What's a thread? A thread is actually an execution queue. When Windows first creates a process, it creates only one thread per process. This thread usually starts execution from the first instruction in the module. If the process later needs more threads, it can explicitly create them.
When Windows receives a command to create a process, it creates the private memory address space for the process and then it maps the executable file into the space. After that it creates the primary thread for the process.
Under Win32, you can also create processes from your own programs by calling CreateProcess function. CreateProcess has the following syntax:

BOOL CreateProcess(
    LPCTSTR  lpApplicationName,         // pointer to name of executable module
    LPTSTR  lpCommandLine,                 // pointer to command line string
    LPSECURITY_ATTRIBUTES  lpProcessAttributes, // pointer to process security attributes
    LPSECURITY_ATTRIBUTES  lpThreadAttributes, // pointer to thread security attributes
    BOOL  bInheritHandles,                     // handle inheritance flag
    DWORD  dwCreationFlags,                // creation flags
    LPVOID  lpEnvironment,                     // pointer to new environment block
    LPCTSTR  lpCurrentDirectory,        // pointer to current directory name
    LPSTARTUPINFO  lpStartupInfo,    // pointer to STARTUPINFO
    LPPROCESS_INFORMATION  lpProcessInformation  // pointer to PROCESS_INFORMATION 
   );

Don't be alarmed by the number of parameters. We can ignore most of them.

lpApplicationName --> The name of the executable file with or without pathname that you want to execute. If this parameter is null, you must provide the name of the executable file in the lpCommandLine parameter.
lpCommandLine   --> The command line arguments to the program you want to execute. Note that if the lpApplicationName is NULL, this parameter must contain the name of the executable file too. Like this: "notepad.exe readme.txt"
lpProcessAttributes and lpthreadAttributes --> Specify the security attributes for the process and the primary thread. If they're NULLs, the default security attributes are used.
bInheritHandles --> A flag that specify if you want the new process to inherit all opened handles from your process.
dwCreationFlags --> Several flags that determine the behavior of the process you want to created, such as, do you want to process to be created but immediately suspended so that you can examine or modify it before it runs? You can also specify the priority class of the thread(s) in the new process. This priority class is used to determine the scheduling priority of the threads within the process. Normally we use CREATE_NEW_CONSOLE and NORMAL_PRIORITY_CLASS flags.
lpEnvironment --> A pointer to the environment block that contains several environment strings for the new process. If this parameter is NULL, the new process inherits the environment block from the parent process.
lpCurrentDirectory --> A pointer to the string that specifies the current drive and directory for the child process. NULL if  you want the child process to inherit from the parent process.
lpStartupInfo --> Points to a STARTUPINFO structure that specifies how the main window for the new process should appear. The STARTUPINFO structure contains many members that specifies the appearance of the main window of the child process. If you don't want anything special, you can fill the STARTUPINFO structure with the values from the parent process by calling GetStartupInfo function.
lpProcessInformation --> Points to a PROCESS_INFORMATION structure that receives identification information about the new process.  The PROCESS_INFORMATION structure has the following members:

PROCESS_INFORMATION STRUCT
    hProcess          HANDLE ?             ; handle to the child process
    hThread            HANDLE ?             ; handle to the primary thread of the child process
    dwProcessId     DWORD ?             ; ID of the child process
    dwThreadId      DWORD ?             ; ID of the primary thread of the child process
PROCESS_INFORMATION ENDS
Process handle and process ID are two different things. A process ID is a unique identifier for the process in the system. A process handle is a value returned by Windows for use with other process-related API functions. A process handle cannot be used to identify a process since it's not unique.

After the CreateProcess call, a new process is created and the CreateProcess call return immediately. You can check if the new process is still active by calling GetExitCodeProcess function which has the following syntax:

BOOL GetExitCodeProcess(
    HANDLE  hProcess,         // handle to the process
    LPDWORD  lpExitCode  // address to receive termination status
   );

If this call is successful, lpExitCode contains the termination status of the process in question. If the value in lpExitCode is equal to STILL_ACTIVE, then that process is still running.

You can forcibly terminate a process by calling TerminateProcess function. It has the following syntax:

BOOL TerminateProcess(
    HANDLE  hProcess,     // handle to the process
    UINT  uExitCode           // exit code for the process
   );

You can specify the desired exit code for the process, any value you like. TerminateProcess is not a clean way to terminate a process since any dll attached to the process will not be notified that the process was terminated.
 

Content:

The following example will create a new process when the user selects the "create process" menu item. It will attempt to execute "msgbox.exe". If the user wants to terminate the new process, he can select the "terminate process" menu item. The program will check first if the new process is already destroyed, if it is not, the program  will call TerminateProcess function to destroy the new process.

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

.const
IDM_CREATE_PROCESS equ 1
IDM_TERMINATE equ 2
IDM_EXIT equ 3

.data
ClassName db "Win32ASMProcessClass",0
AppName  db "Win32 ASM Process Example",0
MenuName db "FirstMenu",0
processInfo PROCESS_INFORMATION <>
programname db "msgbox.exe",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HANDLE ?
ExitCode DWORD ?                    ; contains the process exitcode status from GetExitCodeProcess call.

.code
start:
        invoke GetModuleHandle, NULL
        mov    hInstance,eax
        invoke GetCommandLine
        invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
        invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,OFFSET MenuName
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,0
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,300,200,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    invoke GetMenu,hwnd
    mov  hMenu,eax
    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
    .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL startInfo:STARTUPINFO
    mov   eax,uMsg
    .IF eax==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF eax==WM_INITMENUPOPUP
        invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
        .if eax==TRUE
            .if ExitCode==STILL_ACTIVE
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
            .else
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
            .endif
        .else
            invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
            invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
        .endif
    .ELSEIF eax==WM_COMMAND
        mov eax,wParam
        .if lParam==0
            .if ax==IDM_CREATE_PROCESS
                invoke GetStartupInfo,ADDR startInfo
                invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
                                        NORMAL_PRIORITY_CLASS,\
                                        NULL,NULL,ADDR startInfo,ADDR processInfo
            .elseif ax==IDM_TERMINATE
                invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
                .if ExitCode==STILL_ACTIVE
                    invoke TerminateProcess,processInfo.hProcess,0
                .endif
            .else
                invoke DestroyWindow,hWnd
            .endif
        .endif
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor    eax,eax
    ret
WndProc endp
end start

Analysis:

The program creates the main window and retrieves the menu handle for future use. It then waits for the user to select a command from the menu. When the user selects "Process" menu item in the main menu, we process WM_INITMENUPOPUP message to modify the menu items inside the popup menu before it's displayed.

    .ELSEIF eax==WM_INITMENUPOPUP
        invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
        .if eax==TRUE
            .if ExitCode==STILL_ACTIVE
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
            .else
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
            .endif
        .else
            invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
            invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
        .endif

Why do we want to process this message? Because we want to prepare the menu items in the popup menu before the user can see them. In our example, if the new process is not started yet, we want to enable the "start process" and gray out the "terminate process" menu items. We do the reverse if the new process is already active.
We first check if the new process is still running by calling GetExitCodeProcess function with the process handle that was filled in by CreateProcess function. If GetExitCodeProcess returns FALSE, it means the process is not started yet so we gray out the "terminate process" menu item. If GetExitCodeProcess returns TRUE, we know that a new process has been started, but we have to check further if it is still running. So we compare the value in ExitCode to the value STILL_ACTIVE, if they're equal, the process is still running: we must gray out the "start process" menu item since we don't want to start several concurrent processes.

            .if ax==IDM_CREATE_PROCESS
                invoke GetStartupInfo,ADDR startInfo
                invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
                                        NORMAL_PRIORITY_CLASS,\
                                        NULL,NULL,ADDR startInfo,ADDR processInfo

When the user selects "start process" menu item, we call GetStartupInfo function to fill in the startupinfo structure that we will pass to CreateProcess function. After that we call CreateProcess function to start the new process.

            .elseif ax==IDM_TERMINATE
                invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
                .if ExitCode==STILL_ACTIVE
                    invoke TerminateProcess,processInfo.hProcess,0
                .endif

When the user selects "terminate process" menu item, we check if the new process is still active by calling GetExitCodeProcess function. If it's still active, we call TerminateProcess function to kill the process.


[Iczelion's Win32 Assembly HomePage]