A First Example

In this example I demonstrate several core concepts, some of which are specific to Microsoft Macro Assembler. We will create a simple hello world program and use both macro shortcuts and manual stack and method call conventions to invoke printf to print the traditional “Hello World” message. In order to demonstrate the usage of structures in macro assembler, I embellish the “Hello World” message with the values stored in a structure. This demonstrates the handling of structures in macro assembler, the calling of printf with variable arguments, and the handling of the stack for cdecl type functions. Outlined below is each key file in the project and the purposes it serves. Download the project file to follow along.

  • explore_masm.inc -> This is the include file where type definitions and prototype declarations live
  • explore_masm.asm -> The assembly file where main and other functions are defined
  • sources -> The build definition file specific to the Windows driver development kit

Each file is annotated with comments describing what each section does and I wont duplicate that documentation here. Instead let’s focus on the core concepts starting with the include file. explore_masm.inc does four things for us in this project. First it declares the structure type MYSTRUCTTYPE

        field1  DWORD   ?
        field2  DWORD   ?

This simple structure contains two 32 bit fields aptly named field1 and field2. The DWORD type means double word. In the olden days a machine word as 16 bits wide (think 8086/80286 processors). When Windows went 32 bits it maintained the type WORD as 16 bits and continued to use DWORD (double word) for 32 bits. The machine word size for 32 bits Windows is 32 bits but to maintain source compatibility with older software, the types remained unchanged even to today. Just remember that WORD means 16 bits and the other types fall into place. DWORD (double) for 32 bits, QWORD (quad) for 64 bits, OWORD (octa) for 128 bits.

Next the include file includes the standard C library so that we can use the the printf function

includelib MSVCRT

Before we can use printf with the INVOKE macro, we have to declare its prototype. This tells INVOKE how to manage the stack including how many arguments the function can take. Without a PROTO definition we can not use the INVOKE macro. The third part of the include file does that for us as follows:

printf    PROTO arg1:Ptr Byte, printlist: VARARG

Finally the include file declares the prototype for our own function setvals which we use to set the values in a struct of type MYSTRUCTTYPE

setvals   PROTO mystruct:Ptr MYSTRUCTTYPE

With these pieces in place we move on to the assembly source file itself. To make use of all of our declarations we first have to include the inc file. The include path is managed by the sources file

include explore_masm.inc

With the prototypes defined and the definitions taken care of in the rest of the asm file, we move on to the wmain function which is our application’s entry point. In wmain let’s look at the usage of the INVOKE macro which gives us C like calling conventions into functions.

INVOKE printf, ADDR szFmtStr,mystrct.field1

Here I am calling printf with two arguments. The first is our format string. This is followed by the argument that will get interpreted by %d in the format string. The INVOKE macro tracks the number of variable arguments we send and will clean up the stack accordingly. Using INVOKE is akin to using printf(szFmtStr, mystrct.field1); in C. This familiar methodology reduces the tedium of calling functions from within assembly and for most cases is the methodology you will want to use when writing your own assembler files. However to understand what INVOKE is really doing let’s look at a manual method for calling printf.

push mystrct.field2
push offset szFmtStr
call printf
add esp,8

Here we manage the stack manually and use printf by issuing the call instruction directly. Remember that function parameters are pushed from right to left on to the stack before calling the function that will use them. If the function is a cdecl function, the caller must clean up the stack and we do that here by adding 8 bytes back to the stack pointer to clean up the two 4 byte arguments we passed to printf.

Both methodologies do the exact same thing. When reading the disassembled instruction stream in the debugger however, you won’t see the INVOKE macro. This macro gets translated by the assembler into the instruction stream highlighted above so it is important to know what instruction stream the macro maps to.

View the README.txt in the project directory for information on how to build the project.

Our main goal is to be able to better read disassembly to improve our debugging skills. To that end the next section focuses on running this example and reading the instruction stream in the debugger to better understand what each function is doing.

  1. Vicente Vasmadjides
    March 19th, 2012 at 06:56 | #1

    Very interesting information!Perfect just what I was searching for!

  1. No trackbacks yet.