Copyright: Copyrifght MASM32 1998-2005 All Rights Reserved


 Introduction to 32 bit Assembler

Copyright The MASM32 Project 1998-2005

Flat memory Model

Protected Mode Memory

Instruction Operands

Opcodes and Mnemonics

Registers

Data Types In Registers

The Stack

Addressing and Pointers

Libraries

Working With Strings

Working With Macros

Basic Loop Design

Working With Structures

Register Preservation Convention


Flat memory Model

A program written in native 32 bit Windows format is created in what is called FLAT memory model which has a single segment that contains both code and data. The programs must be run on a 386 or higher processor.

Differing from earlier 16 bit code that used combined segment and offset addressing with a 64k segment limit, FLAT memory model works only in offsets and has a range of 4 gigabytes. This makes assembler easier to write and the code is generally a lot faster.

All segment registers are automatically set to the same value with this memory model and this means that segment / offset addressing must NOT be used in 32 bit programs that run in 32 bit Windows.

For programmers who have written code in DOS, a 32 bit Windows PE executable file is similar in some respects to a dos COM file, they have a single segment that can contain both code and data and they both work directly in offsets, neither use Segment / Offset addressing.

The defaults in flat-model programs are NEAR code addressing and NEAR data addressing within the range of 4 gigabytes.

The FS and GS segment registers are not normally used in application programs but are used in some instances by the operating system.


Protected Mode Memory

In real mode operating systems such as DOS, it was possible to overwrite sections of the operating system code if a program was not correctly written and this would crash the operating system and the computer required a reboot to run again.

If you were writing code that wrote directly to the CGA video memory area starting at B800 hex and the code had an loop error in it, it could overwrite the entire area above it until the maximum address range in DOS was reached. This overwrote everything including the BIOS settings in memory.

Protected mode memory is designed to prevent this from happening. A protected mode memory manager controls the address range that an application can write to and it will terminate the application if it tries to write in an address range that it does not have access to.

This style of memory management was available in 16 bit Windows but because of the method that 16 bit Windows simulated multitasking, it was possible to overwrite sections of memory that were owned by other applications or the operating system before the errant program crashed.

Depending on what was overwritten in the operating system, some other program that had no errors in it would call an operating system function and both the program and the operating system would crash, more often than not you received the "Blue screen of death" which at least told you something but if you were writing code and made a real mess of it, you received the "Black screen of death", an instant black screen and locked up machine which told you absolutely nothing.

Changes in hardware and the use of hardware based multitasking in 32 bit Windows made protected mode memory managers more reliable. The fundamentals of writing code in a protected mode operating system is to only read and write to memory that you application has access to.

Because assembler allows you to read and write to almost any address range, you need to take precautions on how you handle the addresses that you read and write to. If you allocate a 10k buffer to read to but you try and read 20k, when the address goes past the end of the 10k buffer, you will get a page read fault. You can also get into trouble if you incorrectly dereference a variable in a register and try and read or write to that address as it will be out of the address range that your program has access to.

Page read and write faults are exceptions that are passed from the operating system to the application that makes the error, if the exception is not handled by the application, the operating system closes the application. This offers some measure of protection from applications that make address range errors.

In a multitasking operating system, if this style of protection was not available when multiple programs were running, one program could overwrite the memory of another program that was running which would crash the other running program. The same problem would occur with operating system code, a program that overwrote part of the operating system code would crash the operating system.


Instruction Operands

Operands are the parameters that are used with assembler mnemonics. A mnemonic can have from zero to three operands. With a logical or arithmetic mnemonic with two operands, the right operand is the source and the left operand is the destination.

mnemonic destination, source

Example:

mov eax, 1

MOV is the reserved name for a family of opcodes that perform similar functions.

EAX is the destination operand.

1 is the source operand.

This operation copies the operand "1" into the operand "EAX".

It should be noted that other assemblers for other processors do not use this order of operands.


Opcodes and Mnemonics

At the hardware level in an Intel or compatible processor, instructions are are built into the circuitry and are called "opcodes" . While information can be written at a BIT level, the normal minimum data size is a BYTE.

Code can be written at a BYTE level in HEX notation in a HEX editor but it is a particularly complex method of writing code as it involves knowing a very large number of opcodes and having to handle a peculiarity of Intel processors where numerical data is stored in reverse order to character data.

To copy a 32 bit  value (56 A7 00 FE) into the eax register, you will find the opcode, A1 (MOV EAX) followed by (FE 00 A7 56).

    A1 FE 00 A7 56

To facilitate writing low level code, a system was developed many years ago where groups of similar opcodes were given names that made them a lot more convenient to use. The names are referred to as mnemonics. This is the system used in modern 32 bit assembler.

A mnemonic is a reserved name of a family of opcodes that perform similar tasks in the processor. The actual opcodes are different depending on the size and type of operands being used. For example, with the MOV mnemonic,

    mov eax, Var1 = A1

    mov Var1, eax = A3

The mnemonics give you a far more intuitive way of writing opcodes without any loss in precision. There is an exact correlation between what you write in mnemonics and what you get as finished opcodes.


Registers

Registers are conceptually a special working area within the processor that are faster than memory operands and are designed to work with the processors opcodes.

Registers in an Intel or compatible processor are a very limited resource when writing assembler, you have eight general purpose registers, EAX, EBX, ECX, EDX, ESI, EDI, ESP and EBP. In most instances ESP and EBP should be left alone as they are mainly used for entry and exit of procedures.

This means affectively, you have six 32 bit registers to write you code with plus any other memory locations that are useful in the procedure. ESI and EDI can be used in the normal manner in most instances but neither can be accessed at a BYTE level, you can read the low WORD of ESI as SI and the low WORD of EDI as DI.

Understanding the size of registers and the data that you can place in them is very important when using assembler. A 32 bit Intel or compatible  processor has three native data sizes that can be used by the normal integer instructions, BYTE, WORD and DWORD corresponding to 8 bit, 16 bit and 32 bit.

This can be shown in HEX notation.

BYTE	00

WORD	00 00

DWORD	00 00 00 00

In terms of registers, this corresponds to the three sizes that can be addressed with the normal integer registers. Intel and compatible processors are backwards compatible with older code that uses 8 and 16 bit registers and it is done by accessing any of the general purpose registers in three different ways. Using the EAX register as an example,

AL or AH = 8  bit

AX       = 16 bit

EAX      = 32 bit

This is the schematic of a general purpose 32 bit register.

This schematic is easier to understand at a bit level. Reading from the right side, you have 32 bits in the register, bits 0 to 31. Because of the bit positions for each piece of data that can be accessed in a 32 bit register, AL is called the LOW byte, AH is called the HIGH byte and AX is called the LOW word.

To read the first BYTE in the register (bits 0 to 7) you use,

mov byteval, al	; move 1st byte size value in to variable.

To read the second BYTE in the register (bits 8 to 15) you use,

mov byteval, ah	; move 2nd byte size value into a variable.

If you want to read the first WORD in the register (bits 0 to 15) you use,

mov wordval, ax	; move 1st word size value into a variable.

To get at bits 16 to 31, you must rotate the bits in the register so they can be accessed by the previous instructions. Rotating a 32 bit register in either direction by 16 bits move the LOW 16 bits into the HIGH 16 bits and the HIGH 16 bits into the low 16 bits of the register.

rol eax, 16	; rotate EAX by 16 bits

or

ror eax, 16	; rotate EAX by 16 bits

You cannot put a different size piece of data into a register than its correct size and you cannot mix different register sizes,

mov eax, cl	; this fails as eax is 32 bit, cl is 8 bit

If you need to put the value in CL into a 32 bit register, you must convert it first using a number of different techniques,

movzx eax, cl    ; zero extend unsigned integer

movsx eax, cl    ; sign extend signed integer

In some instances you can use,

xor eax, eax     ; clear eax

mov al,  cl      ; copy cl into al

and there are some older mnemonics that will do the conversions,

mov  al, cl      ; copy cl into al

cbw              ; convert BYTE in AL to WORD in AX

cwde             ; convert WORD in AX to DWORD in EAX


Data Types In Registers

There are three basic types of operands that can be placed in a register, immediate, memory or another register.

An IMMEDIATE operand is usually a number but it can also be a string literal in the form "a" which is converted by the assembler to its ASCII equivalent.

mov al, "a"	; string literal

mov edx, 0	; numeric immediate

A MEMORY operand is an address in memory of some form of data.

mov al, [esi]	; copy byte at address in ESI into AL

mov edx, lpMemvar	; copy variable address into EDX

A REGISTER operand is a register with a value in it.

mov ecx, edx	; copy EDX into ECX

The actions that can be performed are determined by the available opcodes, trying to move one memory operand into another directly does not work because there is no opcode in the processor to do it.

! mov mVar, lpMem	; this fails, no opcode to do it.

If you have a spare registers, you make an indirect copy through that register,

! mov eax, lpMem	; copy memory value into register.

! mov mVar, eax	; copy register into memory value.

If you don't have a spare registers, it can be done another way but it is slightly slower,

! push lpMem	; push memory value onto the stack.

! pop mVar	; pop it back off as another memory value.


The Stack

The stack is conceptually a range of memory that can be used for temporary storage of data from either within a procedure or as the normal method of passing parameters to a procedure. The stack is normally accessed in code by the two mnemonics, PUSH and POP. The stack is accessed on a last on, first off basis which means that the last value pushed onto the stack is the first one to be popped back off the stack.

The next position that can be written on the stack is called the top of the stack. When a piece of data is pushed onto the stack, the processor decrements the stack pointer ESP then writes the data to the top of the stack, when a piece of data is popped back off the stack, the processor reads the data from the top of the stack then increments the stack pointer. The stack address decreases as more data is pushed onto it and the address increases as data is popped back off the stack

The schematic for the stack is as follows, each square represents 1 byte on the stack, the different colors are to demonstrate the different data being pushed and popped and the schematic assumes the top of the stack is the left side of each image.

Existing stack layout.

Pushes

Push a 32 bit value = push eax

Push a 16 bit value = push cx

Push another 16 bit value = push dx

Pops

Pop a 16 bit value = pop dx

Pop second 16 bit value = pop cx

Pop the 32 bit value = pop eax.

If you wrote the code,

mov edx, 100

mov ecx, 500

    ; ---------------------------------

    ; push the 2 values onto the stack

    ; ---------------------------------

push edx	; edx has the value 100

push ecx	; ecx has the value 500

    ; -------------------------------

    ; pop the 2 values off the stack

    ; -------------------------------

pop eax	; eax has the value 500

pop ecx	; ecx has the value 100

32 bit Windows has a convention of which registers must be preserved in a procedure, EAX, ECX and EDX can be modified freely within a procedure but the EBX, ESI and EDI registers are normally preserved if they are used. In most instances, EBP and ESP should be left alone as they are used at the entry and exit of procedures.

If you write a procedure that uses any of the 3 registers EBX, ESI and EDI, you must push them at the beginning of the procedure and at the end of the procedure before the RET instruction. The following code assumes that the procedure uses all 3 registers.

push ebx

push esi

push edi

; write code here

pop edi

pop esi

pop ebx

Balancing the stack

An important consideration when using the stack is to be symetrical in the byte count of what is pushed and what is popped. If the stack is not balanced on exit from a procedure, program execution begins at the wrong address which will almost exclusively crash the program. In most instances, if you push a given data size onto the stack, you must pop the same data size.

Tricks on the stack

The stack is very flexible in what can be pushed and popped. There are a few tricks that are very useful when using the stack, you can push a 32 bit value and then pop two 16 bit values.

    ; -----------------------------------

    ; push a 32 bit value onto the stack

    ; -----------------------------------

push edx

    ; ------------------------------------

    ; pop two 16 bit values off the stack

    ; ------------------------------------

pop ax

pop cx

Even though the pushed data size is different to the popped data size, four bytes have been pushed onto the stack and four bytes have been popped back off the stack so the stack is balanced.

The stack can be used for many different things, you can push a register and pop it later when you need it so that you do not need to allocate a memory variable to put it in. You can use the stack to move a piece of data between memory operands and registers.

push ecx

pop  memVar

or

push memVar

pop  edx

or between two memory variables

push memVar1

pop  memVar2

Instead of using a register,

mov edx, memvar1

mov memvar2, edx

A collection of small tricks of this type free up the number of registers that you can use in your procedures if they are managed carefully.


Addressing and Pointers

An important distinction in assembler is the difference between the ADDRESS of a variable and the VALUE of a variable. The ADDRESS of a variable is WHERE it is located in memory, the VALUE of a variable is what is written AT that ADDRESS.

This is the ADDRESS of the variable in memory

This is the VALUE at that address

The method used in assembler to get the value at an address is a technique called "dereferencing".

mov eax, lpvar	; copy address into eax

mov eax, [eax]	; dereference it

mov nuvar, eax	; copy eax into new variable

Using square brackets around EAX gives access to the information at the address in EAX. This is the case with any 32 bit register. A register enclosed in square brackets is effectively a memory operand. The size of the data accessed at the address is determined by the size of the register used to receive it. In the above example it is a 32 bit value as it uses a 32 bit register but it can be done with 16 and 8 bit values as well using the correct size register.

Pointers

Pointers are a useful high level language abstraction for passing addresses between procedures and performing other types of complex data manipulation.

In assembler, when you use an instruction like,

lea eax, MyVar

you have put the ADDRESS of a variable into the EAX register. When you take the next step and put that ADDRESS into a variable of its own and you have a POINTER to the address.

mov lpMyVar, eax

The mechanics of this process are worth understanding as it can generate errors that are hard to track down. You can pass a pointer to another procedure either by its value,

mov eax, lpMyVar    ; copy the VALUE into eax

push eax            ; push it as a parameter

call MyProcedure    ; call the procedure

or you can pass it by reference,

lea eax, lpMyVar    ; load the ADDRESS into eax

push eax            ; push it as a parameter

call MyProcedure    ; call the procedure

When you pass an address in this manner, you have added an extra level of indirection so at the procedure end, you have a reference to a reference to an address. To get the address in the procedure, you "dereference" the variable to get the original address.

mov eax, lpMyVar

mov eax, [eax]

The original address is now in eax.


Libraries

Libraries are made from modules that are written in a very similar manner to normal code. There are many times when a common piece of code is needed and there is little point in re-inventing the wheel each time that type of code is needed.

This capacity can be performed by keeping large amounts of source code available and inserting the sections of source code into the body of the code that requires the capacity but libraries automate such forms of code in very useful and fast ways that are far less error prone than manually inserting the code.

Libraries make the development of complex objects possible because of the way they work. It is normal in code to call other procedures and this is called "dependency". Manually inserting sections of source code requires that the programmer has to work out the dependencies and find all of the additional code that is required to make the procedure work.

When you build a library of procedures and use them in a program, the program is linked after assembly and the linker resolves the dependencies so that all of the required code is in the final program. The linker will display an error if it cannot find all of the dependencies.

This is not only very convenient in coding terms but it is also very efficient in size terms as the linker only includes the code that is needed, it does not include any other library module if it is not called in the program.

To maximise the advantage of writing libraries, an important consideration needs to be kept in mind, to avoid any uncalled code in your program, you should never bundle a number of procedures together in a module if they are not always used together. If this happens, the module will be included complete with the unused procedures and this makes your code larger than it needs to be for no purpose.

This problem in libraries is called "granularity" and it can be avoided by writing each procedure in its own module unless other procedure are ALWAYS used with it and are not use by any other procedure.

Writing a module is relatively easy, the easiest way is to get a procedure going in normal code first to make sure it is reliable and correctly does what you require. When this is done, you convert the procedure into a module. A module is assembled into an OBJECT MODULE independently of any other code so you must use the INCLUDE directive for any other required files.

      .386                      ; set processor type

      .model flat, stdcall      ; memory model & calling convention

      option casemap :none      ; case sensitive

      ; --------------------------------

      ; Include any needed INCLUDE files

      ; or procedure prototypes.

      ; --------------------------------

      ; include \masm32\include\windows.inc

      ; include \masm32\include\gdi32.inc

      ; include \masm32\include\user32.inc

      ; ---------------------------------------------------------

      ; Write the prototype for the procedure name below ensuring

      ; that the parameter count & size match and put it in your

      ; library include file.

      ; ---------------------------------------------------------

      ; YourModule PROTO :DWORD,:DWORD etc.....

    .code

; -----------------------------------------------------

YourModule proc par1:DWORD,par2:DWORD etc.....

    ret

YourModule endp

; -----------------------------------------------------

The easiest way to build a library from either a single module or multiple modules is to place the collection of source file that are going to be made into a library into a directory and write a batch file that does the work.

@echo off

\masm32\bin\ml /c /coff *.asm

\masm32\bin\lib *.obj /out:yourlib.lib

: The following line works as well

: \masm32\bin\link -lib "*.obj" "/out:yourlib.lib"

Once the library is built, you write an include file that has the prototypes for each module in the library. Using the library procedures in your code then requires that you use the INCLUDE directive for the include file and the INCLUDELIB directive for the library.

You can then call the procedure you have written directly from your code without having to find the source code and any dependencies.


Working With Strings

In 32 bit Windows, string data is almost exclusively zero terminated strings. There are other forms of string data used in 32 bit Windows, OLE string memory in either BYTE size characters or WORD size characters in UNICODE but most Windows API calls work with zero terminated strings. It is worth noting that string manipulation can be performed on any BYTE sequence, not just character data.

This technology of zero terminated strings is reasonably old but it is fundamentally simple in its structure, very economical in memory and processing terms and it can be done in assembler both well and fast.

The form that strings are allocated in the .DATA section is as follows,

MyString db "This is a string",0

The normal data type for zero terminated strings is BYTE data and a zero terminated string can be addressed as a BYTE array in memory. There are many instances when processing string data where no terminator is used, working with binary data requires that you keep track of the length of the BYTE data and this is usually done in a seperate variable.

The older approach is to use the built in string instructions. In 32 bit code, the string instructions use the source index ESI, the destination index EDI and ECX as the loop counter. The string instructions in this prebuilt loop technique are used with the prefix instructions REP/REPE/REPNE which repeat the action of the string instruction until a condition is met.

A simple copy example shows how this is done, it is assumed that the dest buffer is large enough to receive the byte count in source.

source is the address of the of the source buffer to copy

dest is the address of the destination buffer

ln is the byte count to copy

    cld	; set direction flag forward

    mov esi, source	; put address into the source index

    mov edi, dest	; put address into the destination index

    mov ecx, ln	; put the number of bytes to copy in ecx

  ; --------------------------------------------------

  ; repeat copying bytes from ESI to EDI until ecx = 0

  ; --------------------------------------------------

    rep movsb

In this example, MOVSB copies each byte from ESI to EDI and decrements ECX. The exit condition for the REP prefix is when ECX is decremented to zero.

When you copy a zero terminated string, you can write an algorithm that copies until it finds an ascii zero.

    cld	; clear direction flag to read forward

    mov esi, source	; put address into the source index

    mov edi, dest	; put address into the destination index

  label:

    lodsb	; load byte from source into AL and inc ESI

    stosb	; write AL to dest and inc EDI

    cmp al, 0	; see if its an ascii zero

    jne label	; read the next byte if its not

A trick that will make this algorithm run faster is to directly move each byte from the source address to AL and then from AL the the destination address. On Pentium and later processors, it is faster to use MOV/INC than LODSB or STOSB. This is done by "dereferencing" both ESI and EDI so that they function as memory addresses.

It should be noted that the direction flag cld does not effect this method and you can use any 32 bit registers when you are not using the string instructions.

    mov esi, source	; put address into the source index

    mov edi, dest	; put address into the destination index

  label:

    mov al, [esi]	; copy byte at address in esi to al

    inc esi	; increment address in esi

    mov [edi], al	; copy byte in al to address in edi

    inc edi	; increment address in edi

    cmp al, 0	; see if its an ascii zero

    jne label	; jump back and read next byte if not

This code is longer but faster on later processors with dual pipelines due to what is called pairing, when mnemonics can go through the two pipelines in pairs, the code runs nominally twice as fast. The choice of mnemonics in this simple algorithm are small instructions of the type that pair properly so it runs faster than the shorter algorithm using the older string instructions that do not pair.

Anonymous labels

There is a notation in MASM to address the need for always having to create unique label names, especially when a lot of code uses labels in close sequence where their position makes sense without needing to be named. It is called anonymous labels that have the form,

@@:

and they are addressed respectively with @F and @B.

jmp @F means to jump FORWARD to the next occurrence of @@:

jmp @B means to jump BACK to the last occurrence of @@:

Expanding the use of the algorithm

The previous algorithm is a good base for string manipulation that modifies bytes in the string, filters, upper and lower case conversions and other types of byte replacements.

If you want to filter a string so that it only has numbers in it, you test the range of ascii numbers in the string and disallow any others.

ascii "0" is 48

ascii "9" is 57

NumOnly proc source :DWORD, dest:DWORD

    mov ecx, source     ; put address into ecx

    mov edx, dest       ; put address into edx

  @@:

    mov al, [ecx]       ; copy byte at address in ecx to al

    inc ecx             ; increment address in ecx

  ; -----------------------------------------------------

  ; perform byte modification, replacement  or omissions

  ; -----------------------------------------------------

    cmp al, 0           ; test for zero first

    je  @F              ; exit loop on ascii zero

    cmp al, "0"         ; string literal

    jb @B               ; if below 48 "0", jump back

    cmp al, "9"         ; string literal

    ja @B               ; if above 57 "9", jump back

  ; ------------------------------------------------------

    mov [edx], al       ; copy byte in al to address in edx

    inc edx             ; increment address in edx

    jmp @B

  @@:

    mov [edx], al       ; copy ascii zero to address in EDI

    ret

NumOnly endp


Working With Macros

Macro capacity is often referred to as a pre-processor as it allows the programmer to perform operations on the form of code BEFORE it is passed to the assembler. When written correctly, macros can declutter code, automate complex operations in convenient ways and make repetitious tasks simple.

You write a basic macro as follows,

Do_This_Operation MACRO

  ; write your code here

ENDM

You write the name of the macro in your code.

Do_This_Operation

When the pre-processor in the assembler finds the macro, it expands the macro back up to the code that you wrote within the macro. A macro can be written to accept parameters which are inserted into the code you write in the correct places.

      RGB MACRO red, green, blue

        xor eax, eax

        mov al, blue    ; blue

        rol eax, 8

        mov al, green   ; green

        rol eax, 8

        mov al, red     ; red

      ENDM

In this simple macro, you can set the three BYTE values as either 3 immediate numerical operands, three memory operands or three registers.

      RGB 125, 175, 225

                  or

      RGB Byte1, Byte2, Byte3

                  or

      RGB cl, ch, dl

When the macro is called in code, it expands the macro back up to the code you wrote with the parameters you passed to it in the correct places.

Macros differ from library modules n a number of important ways, each macro call directly inserts the macro code into the body of the code so if you call a macro ten times, you get the code ten times.

This is useful if you are writing repetitive code that loads an array prior to a function call and it is also faster than a procedure in that it does not have any stack overhead but the down side is that if the macro is larger in size and is not particularly speed critical, you code size will increase due to the repetition of the macro.

It is usually a programming decision to use either a macro or a procedure depending on which has the greatest advantage in the circumstances that code is used.

Macros can perform reasonably complex operations that have loop structures within them.

; --------------------------------------------------

; macro for making STDCALL procedure and API calls.

; --------------------------------------------------

Scall MACRO name:REQ,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12, \

                     p13,p14,p15,p16,p17,p18,p19,p20,p21,p22

    ;; ---------------------------------------

    ;; loop through arguments backwards, push

    ;; NON blank ones and call the function.

    ;; ---------------------------------------

    FOR arg,<p22,p21,p20,p19,p18,p17,p16,p15,p14,p13,\

             p12,p11,p10,p9,p8,p7,p6,p5,p4,p3,p2,p1>

      IFNB <arg>    ;; If not blank

        push arg    ;; push parameter

      ENDIF

    ENDM

    call name       ;; call the procedure

ENDM

; ------------------------------------------------------------------

This macro works by using a FOR loop that reads the parameter list backwards and if it each is not blank, it pushes the parameter onto the stack before calling the first parameter that is the procedure name.

Macros in part give the programmer the capacity to build their own language and this makes assembler a very flexible way to write fast and efficient code.

Local parameters in a macro

A problem arises if a MACRO has a label in it when the macro is called more than once, you get a duplicate label error. Fortunately there is a simple solution to the problem, MASM has a LOCAL directive that applies to macros that works by the assembler inserting a unique label in its place during the pre-processor stage so that the error does not occur.

This name LOCAL is ambiguous in MASM as MASM also uses the same term in a procedures to designate stack parameters. The two seperate usages should not be confused, a LOCAL in a MACRO has the following form,

MyMacro MACRO Parameter1, Parameter2 etc ...

  LOCAL MyLabel

  ; asm code

  jmp MyLabel

  ; asm code

  MyLabel:

  ; other asm code

ENDM

MASM converts the LOCAL MyLabel to something like ?00001: and the next call to the macro will be ?00002 etc ... This ensures that there is no duplication error. LOCAL can be applied to variables as well.


Basic Loop Design

High speed loops are critical where performance matters and it is here where a lot of time can be wasted if the loop design is not written properly.

At there most basic, a loop is a label followed further down the instruction sequence by a jump back to it.

label:

; assembler code

jmp label

What has to be added to this loop is an exit condition so that it does not run infinitely. Simply adding the extra code will work,

  mov edx, 0

label:

  ; assembler code

  inc edx

  cmp edx, 1000000

  je lblOut

  jmp label

lblOut:

but it is suboptimal code because of the number of instructions it uses to test for the exit condition and to exit. What determines a faster loop design is what the loop will be used for.

If a loop does nothing more than repeat a set number of times, there is a well known optimisation trick that will make the loop run a lot faster.

  mov edx, 1000000

label:

  ; assembler code here

  dec edx

  jnz label

This works because the DEC mnemonic sets the ZERO flag  when the counter decrements to zero. The JNZ mnemonic will not jump to the label under the condition that the zero flag is set. It has a lower instruction count and it outperforms most other loop designs because of it.

When working with BYTE data, you usually need to increment the byte address each iteration of the loop so in terms of instruction count, using the address counter means that you don't have to add the code for a seperate counter.

  mov esi, src      ; put source address in esi

  mov edi, dst      ; put destination address in edi

  mov ecx, count    ; put count in ecx

  add ecx, esi      ; add address in esi to ecx

                    ; to have an exit condition to test

label:

  mov al, [esi]

  inc esi

    ; do something with the byte if needed

  mov [edi], al

  inc edi

  cmp ecx, esi      ; test for exit condition here

  jne label         ; if not equal, loop again

Unrolling a loop

There are times when a well optimised loop may still not be fast enough and that any increase in speed will justify the extra coding required to unroll the loop. Unrolling a loop means doing more in the loop each iteration so that the loop overhead is diminished.

To use a simple example, if you were copying data from one buffer to another at a DWORD at a time and you needed it to go faster again, you could try reading two DWORD reads and writes each iteration of the loop. Note that the examples do NOT handle any remainder after division.

Single DWORD copy.

  mov esi, src

  mov edi, dst

  mov ecx, count  ; count in ecx

  shr ecx, 2      ; divide by 4

label:

  mov eax, [esi]

  add esi, 4

  mov [edi], eax

  add edi, 4

  dec ecx

  jnz label

Unrolled to perform 2 DWORD copies each iteration.

  mov esi, src

  mov edi, dst

  mov ecx, count      ; count in ecx

  shr ecx, 3          ; divide by 8

label:

  mov eax, [esi]      ; read 1st DWORD

  mov ebx, [esi + 4]  ; read 2nd DWORD

  mov [edi], eax      ; write 1st DWORD

  mov [edi + 4], ebx  ; write 2nd DWORD

  add esi, 8          ; add 8 to esi

  add edi, 8          ; add 8 to edi

  dec ecx

  jnz label

This technique theoretically reduces the loop overhead by half. There are of course many other factors such as memory access speed but the final way of seeing if the rewritten algorithm is any faster is to benchmark it against the single DWORD copy version.

You can unroll this loop by a factor of four but the increase in speed reduces with the increase in the number of times the loop is unrolled so there may be no benchmarked gain in doing the extra coding, especially in a more complex loop.

Mnemonics to avoid

The Intel processors have a built in set of instructions for loops but they are very poorly optimised and run very slowly in comparison to CMP/JMP.

LOOP, LOOPE, LOOPZ, LOOPNZ, LOOPNE

These are leftover technology from the DOS era. In each instance you can write faster code by manually incrementing or decrementing the loop counter, comparing the result and jumping back to a label.

String instructions without the REP family of prefixes are suboptimal performers and should be replaced with manually code alternatives.

MOVS, LODS, CMPS, SCAS

These string instructions are normally used with the size specifier at the end, IE. MOVSD, LODSB etc ...

Manually code alternatives are a lot faster in loop code.

JCXZ should be replaced with,

test ecx, ecx

je StartLabel


Working With Structures

When writing code in Windows, it is a common to handle data in blocks for some coding requirements. The normal method is to use a structure to group data in convenient ways so that it can be addressed as a single unit.

A structure is made up of members that have a specified data size. In a commonly used structure RECT, you have four DWORD size members.

    RECT STRUCT

      left    DWORD  ?

      top     DWORD  ?

      right   DWORD  ?

      bottom  DWORD  ?

    RECT ENDS

The notation for each member is membername, datasize, specifier. In most instances, the specifier is ? which means the member is not initialised to a value.

A structure is written in memory as a sequence of members so in the case of a RECT structure, it is written as four sequential DWORD size members in memory.  The members of a structure can be filled in a number of different ways depending on how the structure was originally allocated.

If it is allocated in the .DATA section, it can be initialised to preset values but if it is allocated on the stack as a LOCAL value within a procedure, the values have to be coded into the structure.

    LOCAL Rct :RECT

    ; code

    mov Rct.left,   1

    mov Rct.top,    2

    mov Rct.right,  3

    mov Rct.bottom, 4

It should be noted that a structure member is a memory operand so you cannot directly move another memory operand into it, you must either use a register to copy it or the stack mnemonics, push / pop.

You can now refer to the filled structure as a single unit with ADDR Rct in an invoke directive procedure call. If an API call requires the address of a structure, you fill the structure with the values you require and call the API,

    invoke APIcall,parameter1,parameter2,ADDR Rct

If you write a procedure where you want the values in a structure passed to it, you can pass the structure by using the structures data type in the procedure.

MyProc proc par1:DWORD,par2:DWORD,MyRect:RECT

    mov eax, MyRect.left    ; copy first member into EAX

The individual members can then be accessed by their names in the procedure that receives the RECT as a parameter. You call the proc as follows,

    invoke MyProc,par1, par2, Rct

Nested structures

A method common to 32 bit Windows is the use of nested structures. MASM has a notation for dealing with this type of construction. If you needed a structure that had multiple structures in it,

    MyNestedStruct STRUCT

        item1 RECT  <>

        item2 POINT <>

    MyNestedStruct ENDS

This structure uses the above RECT struct and the following POINT struct.

    POINT STRUCT

      x  DWORD ?

      y  DWORD ?

    POINT ENDS

There are six members in this structure, four from the RECT structure and two from the POINT structure. Allocated on the stack it looks like this,

    LOCAL mns:MyNestedStruct

The six members are,

    mns.item1.left

    mns.item1.top

    mns.item1.right

    mns.item1.bottom

    mns.item2.x

    mns.item2.y

The notation for mns.item2.x means the allocated structure mns , its second item item2 and the first item in the POINT struct x.

Structures can be nested to multiple depths but they all use this basic notation and logic.

Advanced Structure Handling

Increasingly, there is a need to be able to handle structures that are passed as an address and this type of code is becoming more common in Windows code design. MASM has a specialised notation to make this process a lot easier to handle.

If for example you needed to pass the adress of a RECT structure to a procedure, you would normally make the call in the following manner,

invoke MyFunction,ADDR Rct

At the procedure end of this function call you would normally have something like the following,

MyFunction proc lpRect:DWORD

With a simple structure like RECT you can address the seperate parameters manually by putting the address into a register and write to the location of each member,

    mov eax, lpRct

    mov [eax],    DWORD PTR 10

    mov [eax+4],  DWORD PTR 12

    mov [eax+8],  DWORD PTR 14

    mov [eax+12], DWORD PTR 16

This works fine but with more complex structures this becomes much harder to work with and far more error prone.

The alternative is to use a method that MASM has to address the individual members by using the ASSUME directive.

    ASSUME eax:PTR RECT

    mov eax, lpRct

    mov [eax].left,   10

    mov [eax].top,    12

    mov [eax].right,  14

    mov [eax].bottom, 16

    ASSUME eax:nothing

This works by telling the assembler that the EAX register is to be treated like a RECT structure. The ASSUME eax:nothing tells the assembler that the register is no longer being used in this manner.

There is an alternative notation where you can individually "type cast" each member.

    mov eax, lpRct

    mov (RECT PTR [eax]).left,   10

    mov (RECT PTR [eax]).top,    12

    mov (RECT PTR [eax]).right,  14

    mov (RECT PTR [eax]).bottom, 16

The advantages of these techniques is that they use the convenience and reliability of a structure so that you do not have to calculate the offsets of each member and it also uses the normal member names. The downside of these techniques is that they use a register which is not always a desirable consequence. If the register usage is a problem, you will need to allocate LOCAL variables and copy the data from each required member to the variables.


Register Preservation Convention

A long time ago when MS-DOS was the main operating system in Intel based personal computers the main method of interacting with the operating system was done by interrupts and for each interrupt, the registers required to load data for the DOS function were specified with that interrupt.

At the compiler level different manufacturers applied whatever register usage suited their purpose and rarely were these usages ever compatible. In a non re-entrant operating system like MS-DOS this was of little consequence as only one program ran at a time and DOS did not have support for multitasking or external code reuse like a modern DLL, the closest was the old overlay system.

Microsoft introduced 32 bit Windows computing in the early 90s with Windows NT and along with it to match the difference in operating system architecture they also introduced a convention for the use of registers so that applications with different origins could predictably and reliably interact with the operating system.

Intel and compatible hardware from the i386 upwards came with eight (8) general purpose 32 bit registers and to be properly compatible with 32 bit Windows an application had to use the registers in a manner that was consistent with the operating system.

The eight general purpose 32 bit registers are,

eax ecx edx esp ebp ebx esi edi

The registers ESP and EBP are usually used for constructing a stack frame which by its normal requirement preserves and restores these two registers and this leaves 6 general purpose registers which are split into two (2) seperate classes, those which the operating system expect to contain the same value when the application interacts with the operating system and those which can be freely modified without having to be restored.

The three registers that must remain the same for each interaction with the operating system are,

EBX ESI EDI

The three registers that can be freely modified are,

EAX ECX EDX

The convention is not without purpose as it provides a method where the least number of register preservations need to occur between procedures calling other procedures or systme function in the Windows APIs.

Without this efficiency you would see some of the very inefficient methods that are still occasionally used with sub-standard code where all of the registers are preserved and restored and a return value in the EAX register is loaded from a stack variable. This sub-standard method produces larger and slower code with no gain in reliability and only indicates inexperience with the author of such code.

The description above may sound complicated but in practice it is very simple to impliment.

myproc proc args etc .....

    push ebx

    push esi

    push edi

  ; write code that uses ANY of these registers here.

    pop edi

    pop esi

    pop ebx

    ret

myproc endp

This example procedure above uses the normal stack frame so ESP and EBP are already preserved and it also preserves and restores all 3 required registers. While this is safe code, not all procedures need to use any of the three registers so the registers only need to be preserved if the code within the procedure needs to use them which modifies them.

All of the Windows API functions are written this way and you can safely assume that any values you have in EBX ESI and EDI will be the same when the API function return to your calling code.

This means that if you need to have a counter within your procedure, if you preserve any one of these registers, you can use that register as the counter and it will work reliably after an API call or to a function that is written according to the Windows convention.

The reciprocal to this usage is that the 3 registers EAX ECX EDX can be freely modified not only in you own code but any procedure that you call that also uses the operating system convention. This means that if you must store values in any of the 3 mentioned registers and then call other procedures, you must preserve any of EAX ECX EDX before you call another procedure and restore them after it has returned.

In practice you can normally place value you want preserved between calls to other procedures in EBX ESI EDI and use EAX ECX EDX for transient values.

Because the convention allows EAX ECX EDX to be freely modified without having to be restored after, they can also be used to provide up to 3 seperate return values from a procedure. The normal return value is in EAX but you also return values in ECX and EDX.

A common mistake in procedure layout.

With 8 general purpose 32 bit registers where two are normally used for the stack frame, registers are a reasonably scarce resource and one of the common mistakes made when laying out a procedure is to treat registers like variables. In all but the most simple procedures this produces the problem that you run out of registers to hold all of the variables before you get the procedure written.

The solution is to use LOCAL variables to get the procedure running reliably then selectively replace them with any registers that may not be used in the procedure. With careful register reuse you can reduce the number of LOCAL variables by using registers and produce faster code if you get it right.