An Introduction to Assembly Language: Part III

Prerequisite

This article assumes that the reader has installed MASM32. If they have not, it is available from http://www.masm32.com/.

Introduction

In this third and final part of this tutorial, I will cover the common arithmetic functions and some of the macros in MASM that considerably aid in the implementation of Assembler.

Increment and Decrement

The increment and decrement operations are inc and dec. For example:

TestProc proc

mov eax, 5

dec eax
dec eax

mov dl, 10
inc dl
inc dl

ret

TestProc endp

When using decrement dec, if the result is zero, the zero flag is set. This can be used to implement loops. For example:

TestProc proc

   mov ecx, 10
   xor eax, eax    ; efficient way of saying eax=0

LoopStart:
   inc eax
   dec ecx
   jnz LoopStart

   ; eax now equals 10

   ret

TestProc endp

Addition and Subtraction

The addition and subtraction instructions are add and sub. They take the general form of:

add/sub (destination), (source)

You can add registers to registers, registers to constants, and registers to the contents of memory. The size (in bits) of the source and destinations have to be the same. Both affect the flags of the system. For instance, if the result of a sub is zero, the zero flag is set. For example:

AddValues proc dwValue1:DWORD, dwValue2:DWORD

   mov eax, dwValue1
   add eax, dwValue2
   ret

AddValues endp

This method adds the two values passed in and returns the result.

Multiplication and Division

The instructions for multiplication and division are mul and div. Both only operate on the accumulator register (eax) and use the data register (edx) as an overflow. The part of the registers affected are determined by the size of the operand.

The following diagram demonstrates how the accumulator and the data registers fit together when being used by the instructions.

Therefore, to get expected results, it is recommended that you set edx to zero before calling mul or div. For example:

TestProc proc

   mov eax, 10
   xor edx, edx    ; set edx to zero

   mul 10
   div 10

   ret

TestProc endp

Logical Operations

The usual logical operations are convered by or, and, and xor. They take the form as follows:

logical operation (destination), (source)

The size in bits of the source and and destination have to be the same. For instance:

LogicalFunction proc

   xor eax, eax    ; the efficient way of saying eax=0

   mov ax, 100
   mov bx, 5
   and ax, 1
   or ax, bx
   ret

LogicalFunction endp

Bitshift Operations

The instructions shl and shr shift the given register bits left and right by the given bit count. These are highly efficient, and should be preferred over the mul and div instructions for parameters that are powers of two. For example:

ShiftFunction proc

   mov eax, 1
   shl eax, 2    ; shift eax's bits left 2 times :  i.e. eax *= 4
   shr eax, 2    ; shift eax's bits right 2 times : i.e. eax /= 4

   ret

ShiftFunction endp

An Introduction to Assembly Language: Part III

Test Instructions/Loops

There are a number of instructions that can be used to test for particular conditions. These perform the same operations as an arithmetic operation but don't change the values in the registers; they just affect the flags.

The cmp instruction effectively subtracts the source from the destination, but doesn't save the resultant value. For instance:

CmpFunction proc

   mov eax, 100
   cmp eax, 100

   ; jump if equals
   je Equals

   ; not equal
   mov eax, 2
   jmp EndIf

Equals:
   mov eax, 1

EndIf:
   ret

CmpFunction endp

The test instruction performs an and operation on the source and destination operands and sets the flags accordingly without saving the result.

The loop instruction decrements ecx by one, and jumps to a specified location if the result is not zero.

LoopFunction proc

   xor eax, eax
   mov ecx, 10

LoopStart:
   inc eax
   loop LoopStart

   ret

LoopFunction endp

MASM Macros

There are a huge number of macros available in MASM that are designed to make life easier for the Assembler developer. However, I'm only going to cover a few.

The first is the '.if' statement. It provides the ability to compare two operands (using standard C++ operators like =, >=, <=, and so forth:

IfProc proc

   mov eax, 100
   mov ecx, 200

   .if eax == ecx
      ; do something
   .else
      ; do something else
   .endif

   ret

IfProc endp

The second is the .repeat - .until loop. There are various forms of this. .untilcxz decrements ecx by one and continues the loop if the result is not zero. .until zero? continues the loop until the zero flag is set.

LoopProc proc

   xor eax, eax
   mov ecx, 100

   .repeat

      inc eax

   .untilcxz

   ret

LoopProc endp

When performing loops in loops, to free the use of the eax, ebx, and edx registers, the outer loop's ecx value can be pushed and then popped on exiting the inner loop. For example:

LoopInLoopProc proc

   xor eax, eax
   mov ecx, 100

   .repeat

      push ecx
      mov ecx, 100

      .repeat

         inc eax

      .untilcxz

      pop ecx

   .untilcxz

   ret

LoopInLoopProc endp

Calling Functions from Inside Assembler

You call functions inside of Assembler code by using invoke followed by the name of the function and its parameter list seperated by commas. For example:

Function1 proc dwValue:DWORD

   add eax, 100
   ret

Function1 endp

MainFunction proc

   mov eax, 100

   invoke Function1, eax

   ; eax now = 200, i.e. eax += 100
   ret

MainFunction endp

Note that there is a comma between the function name and the first parameter.

Local Memory

MASM allows you to allocate memory local to functions and label it appropriately. This could potentially be considered as local variables, but if you examine the underlying machine language, you'll see that in fact it's just another shorthand form for accessing memory.

You define memory at the start of the function. If you examine the disassembly, you'll see that what actually happens is that a block of static memory is allocated before the first instruction in the function. The memory has a size that is determined by the basic types in MASM; in other words, BYTE, WORD, or DWORD.

ExampleLocalMemory proc

   LOCAL dwValue:DWORD    ; allocates 4 bytes and labels it 'dwValue'
   LOCAL wValue:WORD      ; allocates 2 bytes and labels it 'wValue'
   LOCAL bValue:BYTE      ; allocates 1 byte  and labels it 'bValue'

   xor eax, eax

   mov dwValue, eax
   mov wValue, ax
   mov bValue, al

   ret

ExampleLocalMemory endp

Optimisation

When attempting to write efficient code,it must be considered that not every instruction takes the same time to complete. For instance, mul and div operations are relatively slow compared to the bit-shift operations of shr and shl. A full list of the times of each operation is available in the MASM help files.

When writing efficient code, another consideration is number of instructions involved inside of loops. The fewer the number of instructions, the faster the code will be.

When writing code, memory access is slower than access to registers, so always try to use registers in preference to local function memory.

Also, the efficiency of a jmp depends on the number of bytes to be jumped. This instruction takes offsets of either 8, 16, or 32 bits in size and an 8-bit jump is considerably more efficient than a 32-bit jump. This obviously affects loops: Loops whose instructions size is less than 128 bytes are more efficient than loops containing large blocks of code.

The primary concern is the algorithm itself. The fastest algorithms are always the simplest because they always contain the fewest number of instructions necessary. It is always better to reconsider the algorithm that you are using for a particular task, and if you can trade some accuracy or flexibility in favour of a large improvement in the speed, do so.

There are many, many other considerations when it comes to optimising assembler. Again, the MASM help files are an invaluable source for fine-tuning your code.

Conclusion

I hope that this set of tutorials has been interesting and informative. It is by no means complete because it is only intended as an introduction. For more information, consult the tutorials and help files that come with MASM.

But, I hope that I have demonstrated the fact that Assembler isn't difficult to write and you should be able to add considerable speed to your applications and perform tasks that you never thought possible in real time.

Links to Parts I and II

Part I of this introduction can be found here.

Part II of this introduction can be found here.



About the Author

David McClarnon

He first encountered Windows programming using Visual C++/MFC version 1.5 on Windows 3.11 a very long time ago. He is now a contract developer specialising in .NET/native interop with p/invoke.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Hundreds of millions of users have adopted public cloud storage solutions to satisfy their Private Online File Sharing and Collaboration (OFS) needs. With new headlines on cloud privacy issues appearing almost daily, the need to explore private alternatives has never been stronger. Join ESG Senior Analyst Terri McClure and Connected Data in this on-demand webinar to take a look at the business drivers behind OFS adoption, how organizations can benefit from on-premise deployments, and emerging private OFS …

Most Popular Programming Stories

More for Developers

RSS Feeds