Управление стеком времени выполнения в процедуре

Я работаю над программой, которая содержит две процедуры. Тот, который выталкивает массив из N двойных слов без знака в стек, а тот, который выталкивает N двойных слов без знака из стека и сохраняет их в массиве. Я могу успешно поместить все элементы в стек, но затем процедура не может вернуться к основной программе, потому что указатель стека (esp Регистрация) была изменена.

Я смог вернуться к основному, манипулируя esp зарегистрироваться, чтобы обратный адрес был сохранен, и я перезагружаю этот адрес в esp прежде чем я вернусь. Однако к тому времени, когда следующая процедура вызывается, записи, которые я помещал в стек, были перезаписаны.

Есть ли правильный способ сохранить данные в стеке при работе в процедуре?

Вот часть моего кода:

Основная процедура:

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

Перенос массива в стек в процедуре ArrayToStack:

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

Запись стека в консоль в процедуре StackToArray:

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

2 ответа

Проверьте ваши помещения. В первом параграфе вы говорите о процедуре, которая помещает массив из N двойных слов без знака в стек, но ваш код имеет дело с массивом из N байтов без знака.

Кроме того, я замечаю, что ваш вывод на консоли будет в обратном порядке (к массиву) и что ваш код обнуляет входной массив, когда он будет прочитан. Я сохранил все эти вещи в следующих решениях.

Первые 2 фрагмента сохранятся ECX а также EDX, Они делают клоп EAX,

Конечно, истинное объяснение вашей проблемы с кодированием - увидеть, как стек изменяется с каждым шагом. Внимательно следить!

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

Если нет необходимости сохранять ECX а также EDX регистрируется, но все еще позволяет EAX быть пораженным

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

Когда процедуре P необходимо хранить данные, срок жизни которых превышает время жизни P, данные не могут быть сохранены в стеке в кадре стека P, поскольку, как вы обнаружили, они "уходят", когда возвращается P.

Вот еще пара вариантов, которые будут работать.

  1. Пусть вызывающая процедура (основная) выделит пространство в своем кадре стека для данных и передаст указатель на пространство в P. Это работает, если вызывающая сторона знает или может определить максимальный размер для того, сколько данных P сгенерирует. Вызывающая сторона всегда должна передавать размер вместе с указателем, чтобы P не превышал выделенное пространство.

  2. Используйте malloc (или некоторый эквивалент) в P и возвращайте указатель на данные вызывающей стороне.

Другие вопросы по тегам