William
William

Reputation: 223

How to initialize a local struct in MASM assembly

I want to initialize a local struct on the stack.

Records         STRUCT
data1           DWORD   ?
data2           DWORD   ?
data3           DWORD   ?
data4           DWORD   ?
Records         ENDS

                .CODE
main:
                mov     ebp, esp
                sub     esp, SIZEOF Records

How do I make a named instance of struct Records that I can then use to initialize the data members? I want to use the struct directive with the dot operator and not pointer addition.

I started out by allocating space for the struct, then what should I do?

Upvotes: 4

Views: 3920

Answers (1)

Michael Petch
Michael Petch

Reputation: 47653

I would use the power of the PROC directive to define functions1 and the LOCAL directive to allocate variables in a function. The assembler will deal with the prologue and epilogue code and you can associate a type with the local.

You could write a function that looks like:

main PROC
    LOCAL rec1: Records
    mov rec1.data1, 1
    mov rec1.data2, 2
    mov rec1.data3, 3
    mov rec1.data4, 4

    ret
main ENDP

The code will allocate space on the stack for a variable called rec1 and the remaining lines initialize the structure fields with the values 1,2,3,4. The generated assembly code would look something like:

Segment: _TEXT DWORD USE32 00000033 bytes
0000                            _main:
0000  55                                push            ebp
0001  8B EC                             mov             ebp,esp
0003  83 EC 10                          sub             esp,0x00000010
0006  C7 45 F0 01 00 00 00              mov             dword ptr -0x10[ebp],0x00000001
000D  C7 45 F4 02 00 00 00              mov             dword ptr -0xc[ebp],0x00000002
0014  C7 45 F8 03 00 00 00              mov             dword ptr -0x8[ebp],0x00000003
001B  C7 45 FC 04 00 00 00              mov             dword ptr -0x4[ebp],0x00000004
0022  C9                                leave
0023  C3                                ret

The assembler created a stack frame and computed all the offsets on the stack for you relative to EBP. If you wanted to get the address of rec1 into a register and work with it as a structure you can use LEA to get the effective address of the variable on the stack and use ASSUME to apply a pointer type to it:

main PROC
    LOCAL rec1: Records
    lea eax, [rec1]
    ASSUME eax: ptr Records
    mov [eax].data1, 1
    mov [eax].data2, 2
    mov [eax].data3, 3
    mov [eax].data4, 4
    ret
main ENDP

The generated assembly code would be:

Segment: _TEXT DWORD USE32 00000035 bytes
0000                            _main:
0000  55                                push            ebp
0001  8B EC                             mov             ebp,esp
0003  83 EC 10                          sub             esp,0x00000010
0006  8D 45 F0                          lea             eax,-0x10[ebp]
0009  C7 00 01 00 00 00                 mov             dword ptr [eax],0x00000001
000F  C7 40 04 02 00 00 00              mov             dword ptr 0x4[eax],0x00000002
0016  C7 40 08 03 00 00 00              mov             dword ptr 0x8[eax],0x00000003
001D  C7 40 0C 04 00 00 00              mov             dword ptr 0xc[eax],0x00000004
0024  C9                                leave
0025  C3                                ret

You can use the LOCAL directive to create an array of types as well. You can then initialize elements of individual records in the array. This example allocates space on the stack for an array of 4 Records called rec1 and initializes the the third element (array index 2 since array element numbering is zero based):

main PROC
    LOCAL rec1[4]: Records
    ; Compute address of third Record structure in array
    lea eax, [rec1]
    ASSUME eax: ptr Records
    add eax, 2*(SIZEOF Records)
    ; Initialize the the third Record structure
    mov [eax].data1, 1
    mov [eax].data2, 2
    mov [eax].data3, 3
    mov [eax].data4, 4
    ret
main ENDP

The generated assembly code would be:

Segment: _TEXT DWORD USE32 00000038 bytes
0000                            _main:
0000  55                                push            ebp
0001  8B EC                             mov             ebp,esp
0003  83 EC 40                          sub             esp,0x00000040
0006  8D 45 C0                          lea             eax,-0x40[ebp]
0009  83 C0 20                          add             eax,0x00000020
000C  C7 00 01 00 00 00                 mov             dword ptr [eax],0x00000001
0012  C7 40 04 02 00 00 00              mov             dword ptr 0x4[eax],0x00000002
0019  C7 40 08 03 00 00 00              mov             dword ptr 0x8[eax],0x00000003
0020  C7 40 0C 04 00 00 00              mov             dword ptr 0xc[eax],0x00000004
0027  C9                                leave
0028  C3                                ret

I split the LEA and the ADD into separate instructions to better illustrate what is going on. It can be simplified by removing the ADD and using LEA to add the offset to the base pointer of the rec1 array directly. Preferably it would have been written as:

    lea eax, [rec1 + 2*(SIZEOF Records)]
    ASSUME eax: ptr Records

Structures at Global Scope

If you create structures at global scope (not locals on the stack) you can declare and initialize them this way:

                .DATA
rec2 Records <1,2,3,4>

Each field in the structure is separated by a comma. The structure would appear in the _DATA segment like this:

Segment: _DATA DWORD USE32 00000010 bytes
0000  01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 ................

The data is printed in bytes. Each individual DWORD appears with LSB (least significant byte) to MSB (most significant byte). If displayed as DWORDs they would appear as

0000 00000001 00000002 00000003 00000004

Footnotes

  • 1I used a .MODEL directive at the top of the assembly file to assume C style calling convention by default:

    .386
    .model flat, C
    

Upvotes: 7

Related Questions