Tyler Friesen
Tyler Friesen

Reputation: 73

Manipulating the Runtime Stack within a procedure

I am working on a program that contains two procedures. One that pushes an array of N unsigned double words to the stack, and one that pops N unsigned double words from the stack and stores them in an array. I am able to push all the elements successfully to the stack, but then the procedure cannot return to the main program because the stack pointer (esp register) has been changed.

I have been able to return to main by manipulating the esp register so that the return address is saved, and I reload that address into esp before I return. However, by the time the next procedure is called the entries I pushed to the stack have been overwritten.

Is there a correct way to save data in the stack while working in a procedure?

Here is some of my code:

Main procedure:

main PROC
main_loop:
; Main menu
    mov edx, offset choicePrompt
    call WriteString

    read_input:
        call ReadInt
        jno good_input
        jmp main_loop

    good_input:
        cmp eax, 0
        je create_vector
        cmp eax, 1
        je array_stack
        cmp eax, 2
        je stack_array
        cmp eax, -1
        je end_program

        call crlf
        jmp main_loop

    create_vector:
        call CreateVector
        jmp end_options

    array_stack:
        call ArrayToStack
        jmp end_options

    stack_array:
        call StackToArray
        jmp end_options

    end_options:
        call crlf
        jmp main_loop

end_program:
    mov edx, offset exitPrompt
    call WriteString
    call crlf
exit
main ENDP

Pushing the array to the stack in the ArrayToStack procedure:

mov esi, offset Vector
mov ebx, N
dec ebx
mov eax, 0
mov ecx, -1

push_array_loop:
    inc ecx
    mov al, [esi + ecx]
    push eax
    mov [esi + ecx], byte ptr(0)
    cmp ecx, ebx
    jl push_array_loop

Writing the stack to the console in the StackToArray procedure:

mov eax, N
mov ebx, 4
mul ebx
mov ebx, eax
mov ecx, 0
write_stack_loop:
    mov eax, [esp + ecx]
    add ecx, 4
    call WriteDec
    mov edx, offset spacePrompt
    call WriteString
    cmp ecx, ebx
    jl write_stack_loop

Upvotes: 3

Views: 282

Answers (2)

Sep Roland
Sep Roland

Reputation: 39166

Chech your premises. In your first paragraph you talk about a procedure that pushes an array of N unsigned double words to the stack, but your code deals with an array of N unsigned bytes.

Furthermore I observe that your output on the console will be in reverse order (to the arrray) and that your code zeroes the input array as it gets read. I've kept all of these things in below solutions.

The first 2 snippets will preserve ECX and EDX. They do clobber EAX.

The true explanation for your coding problem is of course to see how the stack gets modified with each step. Watch carefully!

ArrayToStack:

                                                  [ ret ]
                                                  ^ esp

    mov     eax, N                 ; The number of array elements is a runtime value
    dec     eax
    shl     eax, 2
    sub     esp, eax

                             <-- eax = (N-1)*4 -->
                             [     ][ ... ][     ][ ret ]
                             ^ esp

    push    dword ptr [esp + eax]

                      [ ret ][     ][ ... ][     ][     ]
                      ^ esp

    push    ecx
    push    edx

        [ edx ][ ecx ][ ret ][     ][ ... ][     ][     ]
        ^ esp

    xor     ecx, ecx
  ToStack:
    xor     edx, edx
    xchg    dl, [Vector + ecx]     ; Reading byte-sized element while zeroing the source
    mov     [esp + 12 + eax], edx
    inc     ecx
    sub     eax, 4
    jnb     ToStack

        [ edx ][ ecx ][ ret ][ a_N ][ ... ][ a_2 ][ a_1 ]
        ^ esp                ^ esp+12

    pop     edx
    pop     ecx

                      [ ret ][ a_N ][ ... ][ a_2 ][ a_1 ]
                      ^ esp

    ret                            ; EAX ends at -4

                             [ a_N ][ ... ][ a_2 ][ a_1 ]
                             ^ esp


StackToConsoleProcedure:

                      [ ret ][ a_N ][ ... ][ a_2 ][ a_1 ]
                      ^ esp

    push    ecx
    push    edx

        [ edx ][ ecx ][ ret ][ a_N ][ ... ][ a_2 ][ a_1 ]
        ^ esp                ^ esp+12

    xor     ecx, ecx
  FromStack:
    mov     eax, [esp + 12 + ecx*4]
    call    WriteDec
    mov     edx, offset spacePrompt
    call    WriteString
    inc     ecx
    cmp     ecx, N
    jb      FromStack
    shl     ecx, 2                 ; ECX = N*4
    mov     eax, [esp + 8]         ; Fetch return address
    mov     [esp + 8 + ecx], eax

                      <-------- ecx = N*4 ------->
        [ edx ][ ecx ][     ][ a_N ][ ... ][ a_2 ][ ret ]
        ^ esp         ^ esp+8

    mov     eax, ecx
    pop     edx
    pop     ecx

                      <-------- eax = N*4 ------->
                      [     ][ a_N ][ ... ][ a_2 ][ ret ]
                      ^ esp

    add     esp, eax

                                                  [ ret ]
                                                  ^ esp


    ret                            ; EAX ends at N*4

If there's no need to preserve the ECX and EDX registers, but still allowing EAX to get clobbered:

ArrayToStack:
    mov     eax, N
    dec     eax
    shl     eax, 2
    sub     esp, eax
    push    dword ptr [esp + eax]
    xor     ecx, ecx
  ToStack:
    xor     edx, edx
    xchg    dl, [Vector + ecx]
    mov     [esp + 4 + eax], edx
    inc     ecx
    sub     eax, 4
    jnb     ToStack
    ret

StackToConsoleProcedure:
    xor     ecx, ecx
  Fromtack:
    mov     eax, [esp + 4 + ecx*4]
    call    WriteDec
    mov     edx, offset spacePrompt
    call    WriteString
    inc     ecx
    cmp     ecx, N
    jb      FromStack
    shl     ecx, 2
    pop     eax
    add     esp, ecx
    push    eax
    ret

Upvotes: 2

prl
prl

Reputation: 12434

When a procedure P needs to store data that has a lifetime beyond the lifetime of P, the data cannot be stored on the stack within the stack frame of P, because, as you have discovered, it "goes away" when P returns.

Here are a couple other options that will work.

  1. Have the calling procedure (main) allocate space in its stack frame for the data, and pass a pointer to the space into P. This works if the caller knows or can determine a maximum size for how much data P will generate. The caller should always pass a size along with the pointer, so P doesn't exceed the space allocated.

  2. Use malloc (or some equivalent) within P and return a pointer to the data to the caller.

Upvotes: 1

Related Questions