You are on page 1of 17

::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::...........................................FEATURE.

ARTICLE Stack Frames and High-Level Calls by mammon_ Last month I covered how to implement high-level calls in Nasm. Since then it has come to my attention that many beginning programmers are unfamiliar with calling conventions and the stack frame; to remedy this I have prepared a brief discussion of these topics. The CALL Instruction -------------------At its most basic, an assembly language call takes this for: push [parameters] call [address] Some assemblers will require that the CALL statement take as an rgument only addresses leading to external functions or addresses created with a macro or directive such as PROC. However, as a quick glance through a debugger or a passing familiarity with Nasm will demonstrate, the CALL instruction simply jumps to an address [often a label in the source code] while pushing the contents of EIP [containing the address of the instruction following the call] onto the stack. The CALL instruction is therefore equivalent to the following code: push EIP jmp [address] The address that has been called will thefore have the stack set up as follows: [Last Parameter Pushed]: DWORD [Address of Caller] : DWORD --- "Top" of Stack [esp] --At this point, anything pushed onto the stack will be on top of [that is, with a lower memory address, since the stack "grows" downwards] the return address. The Stack Frame --------------Note that the parameters to the call therefore cannot be POPed from the stack, as this will destroy the saved return address and thus cause the application to crash upon returning from the call [unless, of course, a chosen return address is PUSHed onto the stack before returning from the call]. The logical way to reference these parameters, then, would be as offsets from the stack pointer: [parameter 2] : DWORD esp + 8 [parameter 1] : DWORD esp + 4 [Address of Caller]: DWORD esp ----- "Top" of Stack [esp] ----In this example, "parameter 1" is the parameter pushed onto the stack last, and "parameter 2" is the parameter pushed onto the stack before parameter 1, as follows: push [parameter 2] push [parameter 1] call [procedure] The problem with referring to parameter as offsets from esp is that esp will change whenever a value is PUSHed onto the stack during the routine. For this reason, it is standard for routines which take parameters to set up a "stack frame".

In a stack frame, the base pointer [ebp] is set equal to the stack pointer [esp] at the start of the call; this provides a "base" address from which parameters can be addressed as offsets. It is assumed that the caller had a stack frame also; thus the value of ebp must be preserved in order to prevent causing damage to the caller. The stack frame usually takes the following form: push ebp mov ebp, esp ... [actual code for the routine] ... mov esp, ebp pop ebp This means that once the stack frame has been entered, the stack has the following structure: [parameter 2] : DWORD ebp + 12 [parameter 1] : DWORD ebp + 8 [Address of Caller]: DWORD ebp + 4 [Old Base Pointer] : DWORD ebp ----- Base Pointer [ebp] --------- "Top" of Stack [esp] ----The use of the base pointer also allows space to be allocated on the stack for local variables. This is done by simply subtracting bytes from esp; since esp is restored when the stack frame is exitted, this space will automatically be deallocated. The local variables are then referred to as *negative* offsets from ebp; these may be EQUed to meaningful symbol names in the source code. A routine that has 3 local DWORD variables would take the following form: Var1 EQU [ebp-4] Var2 EQU [ebp-8] Var3 EQU [ebp-12] ;provide meaningful names for the variables push ebp mov ebp, esp sub esp, 3*4 ;3 DWORDs at 4 BYTEs apiece ... [actual code for the routine] ... mov esp, ebp pop ebp This routine would then have the following stack structure after the allocation of the local variables: [parameter 2] : DWORD ebp + 12 [parameter 1] : DWORD ebp + 8 [Address of Caller]: DWORD ebp + 4 [Old Base Pointer] : DWORD ebp ----- Base Pointer [ebp] ----[Var1] : DWORD ebp - 4 [Var2] : DWORD ebp - 8 [Var3] : DWORD ebp - 12 ----- "Top" of Stack [esp] ----The stack frame has can also be used to provide a call trace, as it stores the base pointer of [and thus a pointer to the caller of] the caller. Assume that a program has the following flow of execution: proc_1: push dword call1_p2 push dword call1_p1 call proc_2 ________proc_2: push call2_p1 call proc_3 ________________proc_3: push call3_p1 call proc_4 Upon creation of the stack frame in proc_4, the stack has the following structure: [call1_p2] : DWORD ebp + 36 [call1_p1] : DWORD ebp + 32 [Return Addr of Call1] : DWORD ebp + 28

[Old Base Pointer] : DWORD ebp + 24 ---- Base Pointer of Call 1 ---[call2_p1] : DWORD ebp + 20 [Return Addr of Call2] : DWORD ebp + 16 [Base Pointer of Call1]: DWORD ebp + 12 ---- Base Pointer of Call 2 ---[call3_p1] : DWORD ebp + 8 [Return Addr of Call3] : DWORD ebp + 4 [Base Pointer of Call2]: DWORD ebp ----- Base Pointer [ebp] --------- "Top" of Stack [esp] ----As you can see, for each previous call the return address is [ebp+4], where ebp is the address of the saved base pointer for the call previous to that one. Thus, if one could traverse the history of stack frames as follows: mov eax, ebp ; eax = address of previous ebp mov ecx, 10 ; trace the last 10 calls loop_start: mov ebx, [eax+4] ; ebx = return address for call call print_stack_trace mov eax, [eax] ; step back one stack frame loop loop_start This is exceptionally useful for exception handling; the handling function will be able to print out a stack history to aid debugging. This principle can also be applied in conjunction with debugging code [for example, the Win32 debug API] to create a utility which will trace the calls [in reality, the stack frames of the calls] made by a target. Essentially, this would boil down to the following logic: 1) Breakpoint on changes to EBP 2) On Break, get return address [ebp+4] 3) Get instruction prior to return address 4) Print or log the instruction Note that this can be enhanced to resolves symbol names in the logged CALL instruction, such that local or API address labels [e.g. GetWindowTextA] can be logged rather than just the address itself. The ENTER Instruction --------------------The ENTER instruction is used to create a stack frame with a single instruction; it is equivalent to the code push ebp mov ebp, esp The ENTER instruction takes a first parameter that specifes the number of bytes to reserve for local variables; an optional second parameter gives the nesting level [0-31] of the current stack frame in the overall program structure. This is often used by high-level languages to save call trace information for error handlers, as it specifies the number of additional [previous] stack frame pointe rs to save on the stack. The RET Instruction ------------------Any routine which is accessed by a CALL instruction must be terminated with a return [RET] instruction. As one can see from the operation of the CALL instruction, if you were to attempt to circumvent the RET instruction by JMPing to the retrun address, the stack would still be corrupted. The RET statement is roughly equivalent to the following code: pop EIP Note that the RET must take place after exiting the stack frame in order to avoid corruption of the stack.

The LEAVE Instruction --------------------The LEAVE instruction is used to exit a stack frame created with the ENTER instruction; it is equivalent to the code mov esp, ebp pop ebp The LEAVE instruction takes no parameters and still requires a RET statement to follow it. High-level Language Calling Conventions --------------------------------------At this point one may wonder what has happened to the parameters pushed onto the stack prior to the call. Are they still on the stack after the RET, or have they been cleared? Since the parameters cannot be POPed from the stack while within the call, they still are on the stack at the RET instruction. At this point the programmer has two options. They can have the caller clean up the stack by adding the number of bytes pushed to esp immediately after the call: push dword param2 push dword param1 call procedure add esp, 2 * 4 ;2 DWORDs at 4 BYTEs apiece Or they can clear the stack by passing to the RET instruction the number of bytes that need to be cleared: push dword param2 push dword param1 call procedure ... procedure: push ebp mov ebp, esp ... mov esp, ebp pop ebp ret 8 ;2 DWORDs at 4 BYTEs apiece Which method is chosen is left up to the programmer; however, when writing a library or API, one must make clear who is responsible for cleaning up the stack. In addition, when interfacing with high-leve languages, one also has to make clear which order the parameters are to be pushed in. For this reason there are calling conventions for the high-level languages. The C calling convention is used to interface with the C and C++ programming languages; it is used in the standard C library and in Unix APIs. It pushes the parameters from right to left, and does not clean up the stack upon return from the call. A call to a C-style routine would look as follows: ;corresponds to the C code ;procedure(param1, param2) push dword param2 push dword param1 call procedure add esp, 8 A C-style routine would have the following structure: push ebp mov ebp, esp ... mov esp, ebp pop ebp ret

The Pascal calling convention is used interface with the Pascal, BASIC, and Fortran programming languages; it is used in the Win16 API. It pushes the parame ters from left to right, and cleans up the stack upon return from the call; as such it is the opposite of the C convention. A call to a Pascal routine would look as follows: ;corresponds to the C code ;procedure(param1, param2) push dword param1 push dword param2 call procedure A Pascal-style routine would have the following structure: push ebp mov ebp, esp ... mov esp, ebp pop ebp ret 8 ;clear the 2 dword parameters The Stdcall ["standard call" or __stdcall] calling convention is a combination of the C and Pascal conventions; it is used in the Win32 API. It pushes the parameters from right to left, and cleans the stack upon return from the call. A call to a Stdcall routine would look as follows: ;corresponds to the C code ;procedure(param1, param2) push dword param2 push dword param1 call procedure A Stdcall-style routine would have the following structure: push ebp mov ebp, esp ... mov esp, ebp pop ebp ret 8 There is also a Register calling convention [also called "fastcall"] which uses registers rather than the stack to pass parameters. The first parameter is passed in eax, the second in EDX, and the third in EBX; subsequent parameters are passed via the stack. A call to a Register routine would look as follows: ;corresponds to the C code ;procedure(param1, param2, param3) mov eax, param1 mov edx, param2 mov ebx, param3 call procedure Note that there is no defined standard method of clearing the stack ro the Register convention; however most implemntations clear the stack in the Pascal style.

---------------------------------------------------------------------------------------------------

::/ \::::::. :/___\:::::::.

/| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING WndProc, The Dirty Way by X-Calibre of Diamond I assume you all know what a WndProc is, and what you need it for. Let me give you a quick example of a WndProc: WndProc PROC hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg == WM_DESTROY INVOKE PostQuitMessage, NULL .ELSE INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam ret .ENDIF xor eax, eax ret WndProc ENDP This generates the following code: push ebp mov ebp, esp cmp jne dword ptr [ebp+0C], WM_DESTROY @@notDestroy ; Create stack frame ; Why does MASM use 'leave', ; but not 'enter'? ; ebp+0C is uMsg

push NULL Call PostQuitMessage jmp @@exitFromDestroy @@notDestroy: push [ebp+14] push [ebp+10] push [ebp+0C] push [ebp+08] Call DefWindowProcA leave ret 0010 @@exitFromDestroy: xor eax, eax leave ret 0010 ; ; ; ; ; ; ebp+14 is lParam epb+10 is wParam ebp+0C is uMsg ebp+08 is hWnd Let Windows handle the other messages

; Remove stack frame ; Remove function arguments ; from stack and return ; ; ; ; Return 'FALSE' Remove stack frame Remove function arguments from stack and return

Looks nice, and works fine... But, it builds a stack frame, even though we are not using local variables. And if you code in a good fashion, there almost never will be ...after all, this procedure is just a messagehandler, and to keep your code tidy, you will not put all the code in here, but in separate procedure s, which you will call from here.

There's only one reason why MASM builds a stack frame for a function: The function has a prototype for a hll call. A hll call uses the stack to transfer its arguments. So, all we have to do, is remove the prototype. That's easy: Just don't tell MASM that this function uses any arguments. This simple tweak will do the trick: WndProc ... WndProc PROC ENDP

The arguments will still be passed to the function, since that part of the code is in the Windows kernel, and has not changed. Be careful though: Since MASM does not know that there are arguments on the stack, it no longer cleans up the stack. You have to specify that yourself. Now we have a slight problem: How can we access the arguments now? The answer is surprisingly easy: We create aliases for the addresses relative to the stack pointer (esp). MASM does the same, except that it uses the base pointer since it created a stack frame, and saved the original stack pointer in ebp. Knowing that Windows hll calls always push the arguments in reverse order, and that the return address is stored on the stack aswell, we can devise these indices for our parameters: hWnd uMsg wParam lParam EQU EQU EQU EQU dword dword dword dword ptr ptr ptr ptr [esp][4] [esp][8] [esp][12] [esp][16]

There, now we can refer to the arguments as usual. There's 1 drawback however: Since the indices are relative to esp, they are only valid when esp is not touched. In other words: Don't try to push or pop anything and then use these arguments again. They can be used if you push some variables, then pop them again before you access any of these arguments again, because the stack pointer will be at the correct position again. Let's say you need to use the stack again (eg. for an INVOKE), so the indices will be invalidated. You might think that the only option then is to save the stack pointer again, so we're back to the stack frame... It's an option, but not the best one. Namely, ebp is a non-volatile register, and needs to be saved and restored after use. But, there are more registers in the CPU, and most of them are volatile. How about using esi for example? WndProc PROC mov esi, esp hWnd EQU uMsg EQU wParam EQU lParam EQU ... WndProc ENDP

dword dword dword dword

ptr ptr ptr ptr

[esi][4] [esi][8] [esi][12] [esi][16]

And if you leave the stack as you found it (which should always be the case with decent code), you don't even need to restore esp again. If you got dirty and the stack still contains variables you don't want anymore, then this is enough for a clean exit:

WndProc ... mov ret

PROC esp, esi 4 * sizeof dword ; ; ; ; As I mentioned earlier, we have to clean the stack ourselves. We had 4 dword arguments, so this does the trick

WndProc

ENDP

Still less code, and thus faster than the original. And just as rigid. You have one register less to use during the WndProc, but as I said earlier, there shouldn't be too much code here, so should be able to spare the register. Well, there's just 1 more thing that can be done with this tweaked WndProc. Namely, if you leave the stack as you found it, the arguments for the DefWindowProc are already in place, and the return address of our caller is there too. So basically we can just jump to it without any further ado. The resulting WndProc that is equivalent to the original one will look like this then: WndProc PROC hWnd EQU uMsg EQU wParam EQU lParam EQU dword dword dword dword ptr ptr ptr ptr [esp][4] [esp][8] [esp][12] [esp][16]

.IF uMsg == WM_DESTROY INVOKE PostQuitMessage, NULL .ELSE jmp DefWindowProc .ENDIF xor ret WndProc eax, eax 4 * sizeof dword ENDP ; Be sure to clean that stack!

Yes, much shorter, and faster. Let's take a look at the generated code to get a better understanding of how much shorter it actually is: cmp jne dword ptr [esp+08], WM_DESTROY @@noDestroy

push NULL Call PostQuitMessage jmp @@exitFromDestroy @@noDestroy: Jmp DefWindowProcA @@exitFromDestroy xor eax, eax ret 0010 If you code it 'by hand' instead of with the .IF statement, there's another tweak we can pull, but the rest looks great, doesn't it? Of course these stunts can be applied to other procedures as well. Be careful, and use them in good health.

---------------------------------------------------------------------------------------------------::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING Structured Exception Handling under Win32 by Chris Dragan Structured Exception Handling is a powerful feature of all Win32 platforms that allows a program to recover from any critical errors like BOUND, divide overflow, page missing or general protection fault. It is documented only for C-level usage (try-except/finally syntax), and no documentation for low level languages exists. Therefore I will try to show how to use it. The starting point for Structured Exception Handling, SEH, is the Thread Info Block. TIB, as almost all the other structures, is described in winnt.h file that comes with PlatformSDK. struc NT_TIB ExceptionList StackBase StackLimit SubSystemTib FiberDataOrVersion ArbitraryUserPointer Self dd dd dd dd dd dd dd ? ? ? ? ? ? ? ; ; ; ; ; ; ; Used by SEH Used by functions to check for stack overflow ? ? ? Linear address of the TIB

ends

TIB is accessible at address fs:0. NT_TIB.Self contains linear address of TIB, base of FS segment. When an exception occurs, the system uses (dword)fs:0, NT_TIB.ExceptionList to find an exception handler and execute it. The exception list entry is very simple: struc E_L_ENTRY Next ExceptionHandler Optional EntryTerminator dd dd db dd ? ; Points to next entry in the list ? ; User callback - exception hook X dup (?) ; Exception Handler data -1 ; Optional

ends C compilers usually keep some additional information in E_L_ENTRY.Optional field of varying size and usually terminated with (dword)-1. Both .Optional and .EntryTerminator fields are not required. Before calling an exception handler, the exception manager pushes ExceptionRecord and ContextRecord onto the stack. These structures identify an exception and processor state before it. The exception manager adds also its own entry to the exception list. Exception handler is in fact a typical callback. It is not however installed by any API function, but appended in E_L_ENTRY into the exception list. EXCEPTION_DISPOSITION __cdecl _except_handler ( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ); The exception handler uses C-style calling convention, it does not release arguments while returning. The most important parameters are ExceptionRecord and ContextRecord, described at the end of this text, that point to the pushed corresponding structures. I do not have yet any idea what is the purpose of EstablisherFrame and DispatcherContext. struc EXCEPTION_RECORD ExceptionCode ExceptionFlags ExceptionRecord ExceptionAddress NumberParameters ExceptionInformation dd dd dd dd dd dd ? ; ? ? ; ? ; ? ; 15 dup See at the end of this text ? Linear address of faulty instruction Corresponds to the field below (?) ; ?

ends Exception flags are: EXCEPTION_NONCONTINUABLE EXCEPTION_UNWINDING EXCEPTION_UNWINDING_FOR_EXIT = 1 = 2 = 4

The exception handler has two possible ways of proceeding. It can return to the exception manager, or it can unwind the stack and continue the program. In the first case it has to return one of the following values:

enum

EXCEPTION_DISPOSITION \ ExceptionContinueExecution ExceptionContinueSearch ExceptionNestedException ExceptionCollidedUnwind

= = = =

0,\ 1,\ 2,\ 3

The value of zero forces the exception manager to continue the program at saved in context cs:eip, which may be altered by the exception handler. The value of 1 causes the exception manager to call another exception handler in the exception list. Values 2 and 3 inform the exception manager that an error occured - an exception-in-exception happened, or the handler wanted to unwind the stack during another handler of higher instance was doing this already. The other case can be determined if one of .ExceptionFlags is EXCEPTION_UNWINDING or EXCEPTION_UNWINDING_FOR_EXIT. While appending a new exception handler to the exception list, a common practice is to push new E_L_ENTRY onto the stack. This way unwinding the stack can be done simply by skipping the exception manager's entry and restoring the stack pointer. Here is an example of exception handling. ----Start-of-file------------------------------------------------------------ideal p686n model flat, stdcall O equ <offset> dd dd dd dd dd dd ? ? ? ? ? 15 dup (?)

struc EXCEPTION_RECORD ExceptionCode ExceptionFlags ExceptionRecord ExceptionAddress NumberParameters ExceptionInformation ends

procdesc wsprintfA c :dword, :dword, :dword:? procdesc MessageBoxA :dword, :dword, :dword, :dword procdesc ExitProcess :dword udataseg ExCode szCode dataseg szWindowTitle db 'Exception code', 0 szFormat db '%0X', 0 codeseg proc main ; Install exception handler push O ExceptionHandler push [dword fs:0] ; E_L_ENTRY.Next mov [fs:0], esp ; Append new E_L_ENTRY dd ? db 12 dup (?)

; Cause Invalid Opcode exception ud2 _Continue: endp proc ExceptionHandler c ExceptionRecord, EF, ContextRecord, DC ; Save exception code mov eax, [ExceptionRecord] mov ecx, [(EXCEPTION_RECORD eax).ExceptionCode] mov [ExCode], ecx ; Unwind the stack mov eax, [fs:0] mov esp, [eax] pop [dword fs:0] add esp, 4 jmp _Continue endp end main ----End-of-file--------------------------------------------------------------The above source should be compiled with TASM 5.0r or later like this: tasm32 /ml except.asm tlink32 /x /Tpe /aa /c /V4.0 except.obj,,, LIBPATH\import32.lib And here are other important constants and structures, all defined in winnt.h PlatformSDK file. Exception codes: ---------------STATUS_SEGMENT_NOTIFICATION STATUS_GUARD_PAGE_VIOLATION STATUS_DATATYPE_MISALIGNMENT STATUS_BREAKPOINT STATUS_SINGLE_STEP STATUS_ACCESS_VIOLATION STATUS_IN_PAGE_ERROR STATUS_INVALID_HANDLE STATUS_NO_MEMORY STATUS_ILLEGAL_INSTRUCTION STATUS_NONCONTINUABLE_EXCEPTION STATUS_INVALID_DISPOSITION STATUS_ARRAY_BOUNDS_EXCEEDED STATUS_FLOAT_DENORMAL_OPERAND STATUS_FLOAT_DIVIDE_BY_ZERO STATUS_FLOAT_INEXACT_RESULT STATUS_FLOAT_INVALID_OPERATION STATUS_FLOAT_OVERFLOW STATUS_FLOAT_STACK_CHECK STATUS_FLOAT_UNDERFLOW STATUS_INTEGER_DIVIDE_BY_ZERO STATUS_INTEGER_OVERFLOW = = = = = = = = = = = = = = = = = = = = = = 040000005h 080000001h 080000002h 080000003h 080000004h 0C0000005h 0C0000006h 0C0000008h 0C0000017h 0C000001Dh 0C0000025h 0C0000026h 0C000008Ch 0C000008Dh 0C000008Eh 0C000008Fh 0C0000090h 0C0000091h 0C0000092h 0C0000093h 0C0000094h 0C0000095h ; ; ; ; Exception Manager's entry Our entry Restore fs:0 Skip ExHandler address ; Display exception code and call wsprintfA, O call MessageBoxA, call ExitProcess, quit szCode, O szFormat, [ExCode] 0, O szCode, O szWindowTitle, 0 0

STATUS_PRIVILEGED_INSTRUCTION STATUS_STACK_OVERFLOW STATUS_CONTROL_C_EXIT STATUS_FLOAT_MULTIPLE_FAULTS STATUS_FLOAT_MULTIPLE_TRAPS STATUS_ILLEGAL_VLM_REFERENCE Context flags: -------------CONTEXT_i386 CONTEXT_i486 CONTEXT_CONTROL CONTEXT_INTEGER CONTEXT_SEGMENTS CONTEXT_FLOATING_POINT CONTEXT_DEBUG_REGISTERS CONTEXT_EXTENDED_REGISTERS CONTEXT_FULL Context structure: -----------------struc CONTEXT ContextFlags Dr0 Dr1 Dr2 Dr3 Dr6 Dr7 ControlWord StatusWord TagWord ErrorOffset ErrorSelector DataOffset DataSelector RegisterArea Cr0NpxState SegGs SegFs SegEs SegDs Edi Esi Ebx Edx Ecx Eax Ebp Eip = = = = = =

= = = = = =

0C0000096h 0C00000FDh 0C000013Ah 0C00002B4h 0C00002B5h 0C00002C0h

= 000010000h = 000010000h (CONTEXT_i386 (CONTEXT_i386 (CONTEXT_i386 (CONTEXT_i386 (CONTEXT_i386 (CONTEXT_i386 or or or or or or 1) ; 2) ; 4) ; 8) ; 16); 32); SS:ESP, CS:EIP, EFLAGS, EBP EAX, EBX,..., ESI, EDI DS, ES, FS, GS 387 state DB 0-3,6,7 cpu specific extensions

= (CONTEXT_CONTROL or CONTEXT_INTEGER or\ CONTEXT_SEGMENTS)

dd ? dd dd dd dd dd dd dd dd dd dd dd dd dd dt dd dd dd dd dd dd dd dd dd dd dd ? ? ? ? ? ? ? ? ? ? ? ? ? 8 dup (?) ? ? ? ? ? ? ? ? ? ? ?

; CONTEXT_??? flags ; Debug registers

; FPU context

; Segment registers

; Integer registers

dd ? dd ?

; Control registers

SegCs EFlags Esp SegSs ExtendedRegisters ends

dd dd dd dd

? ? ? ?

db 512 dup (?)

Additional word --------------This article was posted on comp.lang.asm.x86. Especially thanks to Michael Tippach for pointing out some exception flags. My web page is at http://ams.ampr.org/cdragan/

::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING Standardizing Win32 Callback Procedures by Jeremy Gordon This short article describes my preferred method for coding CALLBACK procedures in a large assembler program for Windows 32. First I describe what Win32 callback procedures are, and then get down to some code. At run time the Win32 system will call your program on a regular and frequent basis. The procedures you supply for the system to call are called CALLBACK procedures. Here are examples of when these are used:1. To manage a window you created. In this case the system will send many messages to the Window Procedure for the window. The Window Procedure is the code label you provide when you register your window class (by calling RegisterClass). For example the message WM_SIZE is sent by the system when the window is resized. 2. To inform the owner of a child window of events in the child window. For example WM_PARENTNOTIFY (with a notify code) is sent to the Window Procedure of the owner of a window when the child window is being created or destroyed, or if the user clicks a mouse button while the cursor is over the child window. 3. To inform the owner of a common control of events in the control. For example if you create a button owned by your window the Window Procedure for that window receives BN_CLICKED messages if the button is clicked. 4. Messages sent to a dialog you have created. These are messages relating to the creation of the dialog and of the various controls. The dialog procedure is informed of events in the controls. 5. If you "Superclass" or "Subclass" a common control, you receive messages for that common control like a hook procedure but your window procedure has the responsibility of passing them on to the control. 6. If you create "Hook" procedures you can intercept messages about to be sent to other windows. The system will call your hook procedure and will pass the message on only when your hook procedure returns. 7. You can ask the system to provide your program with information to be sent

to a CALLBACK procedure. Examples are EnumWindows (enumerate all top-level windows) or EnumFonts (enumerate all available fonts). In cases 1 to 5 above, just before the system calls the CALLBACK procedure, it PUSHES 4 dwords on the stack (ie. 4 "parameters"). Traditionally the names given to these parameters are:hWnd = handle of window being called uMsg = message number wParam = a parameter sent with the message lParam = another parameter sent with the message. The number of parameters sent to hook procedures and emumeration callbacks varies - see the Window SDK. Since your Window (or Dialog) procedure will need to react in a certain way depending on the message being sent, your code will need to divert execution to the correct place for a particular message. "C" programmers have the advantage of being able to code this simply, using "switch" and "case". Assembler programmers use various techniques. Perhaps the worst if there are a lot of messages to handle is the chain of compares, eg. (in A386 format):MOV EAX,[EBP+0Ch] ;get message number CMP EAX,1h ;see if WM_CREATE JNZ >L2 ;no XOR EAX,EAX ;ensure eax is zero on exit JMP >L32 ;finish L2: CMP EAX,116h ;see if WM_INITMENU JNZ >L4 ;no CALL INITIALISE_MENU JMP >L30 ;correct exit code L4: CMP EAX,47h ;see if WM_WINDOWPOSCHANGED JNZ >L8 and so on ........ To avoid these long chains, assembler programmers have developed various techniques. You will have seen many of these in sample code around Win32 assembler web sites and in the asm journal, using conditional jumps, macros or table scans. I do not wish to compare these various methods, merely to put forward my own current favourite, which I believe has these advantages:1. It works on all assemblers 2. It is modular, ie. the code for each window can be concentrated in a particular part of your source code 3. It is easy to follow from the source code what message causes what result 4. The same function can easily be called from within different window procedures My method results in a very simple Window Procedure as follows (A386 format):WndProc: MOV EDX,OFFSET MAINMESSAGES CALL GENERAL_WNDPROC RET 10h where the messages and functions (specific to this particular window procedure) are set out in a table such as this:-

;---------------------------------------------------------DATA SEGMENT FLAT ;assembler to put following in data section ;--------------------------- WNDPROC message functions MAINMESSAGES DD ENDOF_MAINMESSAGES-$ ;=number to be done DD 312h,HOTKEY,116h,INITMENU,117h,INITMENUPOPUP,11Fh,MENUSELECT DD 1h,CREATE,2h,DESTROY, 410h,OWN410,411h,OWN411 DD 231h,ENTERSIZEMOVE,47h,WINDOWPOSCHANGED,24h,GETMINMAXINFO DD 1Ah,SETTINGCHANGE,214h,SIZING,46h,WINDOWPOSCHANGING DD 2Bh,DRAWITEM,0Fh,PAINT,113h,TIMER,111h,COMMAND DD 104h,SYSKEYDOWN,100h,KEYDOWN,112h,SYSCOMMAND DD 201h,LBUTTONDOWN,202h,LBUTTONUP,115h,SCROLLMESS DD 204h,RBUTTONDOWNUP,205h,RBUTTONDOWNUP DD 200h,MOUSEMOVE,0A0h,NCMOUSEMOVE,20h,SETCURSORM DD 4Eh,NOTIFY,210h,PARENTNOTIFY,86h,NCACTIVATE,6h,ACTIVATE DD 1Ch,ACTIVATEAPP ENDOF_MAINMESSAGES: ;label used to work out how many messages ;---------------------------------------------------------_TEXT SEGMENT FLAT ;assembler to put following in code section ;---------------------------------------------------------and where each of the functions here are procedures, for example:CREATE: XOR EAX,EAX RET ;ensure zero and nc return

and where GENERAL_WINDPROC is as follows:GENERAL_WNDPROC: PUSH EBP MOV EBP,[ESP+10h] ;get uMsg in ebp MOV ECX,[EDX] ;get number of messages to do * 8 (+4) SHR ECX,3 ;get number of messages to do ADD EDX,4 ;jump over size dword L33: DEC ECX JS >L46 ;s=message not found CMP [EDX+ECX*8],EBP ;see if its the correct message JNZ L33 ;no MOV EBP,ESP PUSH ESP,EBX,EDI,ESI ;save registers as required by Windows ADD EBP,4 ;allow for the extra call to here ;now [EBP+8]=hWnd,[EBP+0Ch]=uMsg,[EBP+10h]=wParam,[EBP+14h]=lParam CALL [EDX+ECX*8+4] ;call correct procedure for the message POP ESI,EDI,EBX,ESP JNC >L48 ;nc=don't call DefWindowProc eax=exit code L46: PUSH [ESP+18h],[ESP+18h],[ESP+18h],[ESP+18h] ;ESP changes on push CALL DefWindowProcA L48: POP EBP RET NOTES: ------------------------------------------------------------------------------1. Instead of giving the actual message value, you can, of course, give the name of an EQUATE. For example WM_CREATE EQU 1h enables you to use WM_CREATE,CREATE instead of 1h,CREATE if you wish.

2. It is tempting to keep the message table in the CODE SECTION. This is perfectly possible because the only difference to the Win32 system between the code section and the data section is that the code section area of memory is marked read only, whereas the data section is read/write. However, you may well get some loss of performance if you do this because most processors will read data more quickly from the data section. I performed some tests on this and found that having the table in the code section rather than the data section could slow the code considerably:486 processor - 22% to 36% slower Pentium processor - 94% to 161% slower AMD-K6-3D processor - 78% to 193% slower (but Pentium Pro - from 7% faster to 9% slower) (and Pentium II - from 29% faster to 5% slower) These tests were carried out on a table of 60 messages and the range of results is because tests were carried out varying the number of scans required before a find and also testing a no-find. 3. The procedure names must not be the names of API imports to avoid confusion! For example change SETCURSOR slightly to avoid confusion with the API SetCursor. 4. If a function returns c (carry flag set) the window procedure will call DefWindowProc. An nc return (carry flag not set) will merely return to the system with the return code in eax. (Some messages must be dealt with in this way). 5. You can send a parameter of your own to GENERAL_WNDPROC using EAX. This is useful if you wish to identify a particular window. For example:SpecialWndProc: MOV EAX,OFFSET hSpecialWnd MOV EDX,OFFSET SPECIALWND_MESSAGES CALL GENERAL_WNDPROC RET 10h 6. The ADD EBP,4 just before the call to the function is to ensure that EBP points to the parameters the stack in the same way as if the window procedure had been entered normally. This is intended to ensure that the function will be compatible if called by an ordinary window procedure written in assembler, for example:WndProc: PUSH EBP MOV EBP,ESP ;now [EBP+8]=hWnd,[EBP+0Ch]=uMsg,[EBP+10h]=wParam,[EBP+14h]=lParam 7. A standardized procedure for dealing with messages to a dialog procedure can also be created in the same way, except that it should return TRUE (eax=1) if the message is processed and FALSE (eax=0) if it is not, without calling DefWindowProc. The same coding method can be applied to hooks and to enumerator CALLBACKS although these will vary. jorgon@compuserve.com http://ourworld.compuserve.com/homepages/jorgon

You might also like